//// DomainServer.cpp// domain-server/src//// Created by Stephen Birarda on 9/26/13.// Copyright 2013 High Fidelity, Inc.//// 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 <QDir>#include <QJsonDocument>#include <QJsonObject>#include <QJsonArray>#include <QProcess>#include <QSharedMemory>#include <QStandardPaths>#include <QTimer>#include <QUrlQuery> #include <AccountManager.h>#include <BuildInfo.h>#include <DependencyManager.h>#include <HifiConfigVariantMap.h>#include <HTTPConnection.h>#include <LogUtils.h>#include <NetworkingConstants.h>#include <udt/PacketHeaders.h>#include <SettingHandle.h>#include <SharedUtil.h>#include <ShutdownEventListener.h>#include <UUID.h>#include <LogHandler.h>#include <ServerPathUtils.h> #include "DomainServerNodeData.h"#include "NodeConnectionData.h" int const DomainServer::EXIT_CODE_REBOOT = 234923; const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com"; 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), _httpsManager(NULL), _allAssignments(), _unfulfilledAssignments(), _isUsingDTLS(false), _oauthProviderURL(), _oauthClientID(), _hostname(), _ephemeralACScripts(), _webAuthenticationStateSet(), _cookieSessionHash(), _automaticNetworkingSetting(), _settingsManager(){ qInstallMessageHandler(LogHandler::verboseMessageHandler); LogUtils::init(); Setting::init(); connect(this, &QCoreApplication::aboutToQuit, this, &DomainServer::aboutToQuit); setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); setOrganizationDomain("highfidelity.io"); setApplicationName("domain-server"); setApplicationVersion(BuildInfo::VERSION); QSettings::setDefaultFormat(QSettings::IniFormat); qDebug() << "Setting up domain-server"; // make sure we have a fresh AccountManager instance // (need this since domain-server can restart itself and maintain static variables) DependencyManager::set<AccountManager>(); auto args = arguments(); _settingsManager.setupConfigMap(args); // setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us#ifdef _WIN32 installNativeEventFilter(&ShutdownEventListener::getInstance());#else ShutdownEventListener::getInstance();#endif 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); // if we were given a certificate/private key or oauth credentials they must succeed if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) { return; } setupNodeListAndAssignments(); setupAutomaticNetworking(); if (!getID().isNull()) { setupHeartbeatToMetaverse(); // send the first heartbeat immediately sendHeartbeatToMetaverse(); } // check for the temporary name parameter const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { getTemporaryName(); } _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request _metadata = new DomainMetadata(this); qDebug() << "domain-server is running";} DomainServer::~DomainServer() { // destroy the LimitedNodeList before the DomainServer QCoreApplication is down DependencyManager::destroy<LimitedNodeList>();} void DomainServer::queuedQuit(QString quitMessage, int exitCode) { if (!quitMessage.isEmpty()) { qCritical() << qPrintable(quitMessage); } QCoreApplication::exit(exitCode);} void DomainServer::aboutToQuit() { // clear the log handler so that Qt doesn't call the destructor on LogHandler qInstallMessageHandler(0);} void DomainServer::restart() { qDebug() << "domain-server is restarting."; exit(DomainServer::EXIT_CODE_REBOOT);} const QUuid& DomainServer::getID() { return DependencyManager::get<LimitedNodeList>()->getSessionUUID();} bool DomainServer::optionallyReadX509KeyAndCertificate() { const QString X509_CERTIFICATE_OPTION = "cert"; const QString X509_PRIVATE_KEY_OPTION = "key"; const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; QString certPath = _settingsManager.getSettingsMap().value(X509_CERTIFICATE_OPTION).toString(); QString keyPath = _settingsManager.getSettingsMap().value(X509_PRIVATE_KEY_OPTION).toString(); if (!certPath.isEmpty() && !keyPath.isEmpty()) { // the user wants to use the following cert and key for HTTPS // this is used for Oauth callbacks when authorizing users against a data server // let's make sure we can load the key and certificate QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV); qDebug() << "Reading certificate file at" << certPath << "for HTTPS."; qDebug() << "Reading key file at" << keyPath << "for HTTPS."; QFile certFile(certPath); certFile.open(QIODevice::ReadOnly); QFile keyFile(keyPath); keyFile.open(QIODevice::ReadOnly); QSslCertificate sslCertificate(&certFile); QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8()); _httpsManager = new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this, this); qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT; } else if (!certPath.isEmpty() || !keyPath.isEmpty()) { static const QString MISSING_CERT_ERROR_MSG = "Missing certificate or private key. domain-server will now quit."; static const int MISSING_CERT_ERROR_CODE = 3; QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection, Q_ARG(QString, MISSING_CERT_ERROR_MSG), Q_ARG(int, MISSING_CERT_ERROR_CODE)); return false; } return true;} bool DomainServer::optionallySetupOAuth() { const QString OAUTH_PROVIDER_URL_OPTION = "oauth-provider"; const QString OAUTH_CLIENT_ID_OPTION = "oauth-client-id"; const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET"; const QString REDIRECT_HOSTNAME_OPTION = "hostname"; const QVariantMap& settingsMap = _settingsManager.getSettingsMap(); _oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString()); // if we don't have an oauth provider URL then we default to the default node auth url if (_oauthProviderURL.isEmpty()) { _oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL; } auto accountManager = DependencyManager::get<AccountManager>(); accountManager->setAuthURL(_oauthProviderURL); _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString(); _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV); _hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString(); if (!_oauthClientID.isEmpty()) { if (_oauthProviderURL.isEmpty() || _hostname.isEmpty() || _oauthClientID.isEmpty() || _oauthClientSecret.isEmpty()) { static const QString MISSING_OAUTH_INFO_MSG = "Missing OAuth provider URL, hostname, client ID, or client secret. domain-server will now quit."; static const int MISSING_OAUTH_INFO_ERROR_CODE = 4; QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection, Q_ARG(QString, MISSING_OAUTH_INFO_MSG), Q_ARG(int, MISSING_OAUTH_INFO_ERROR_CODE)); return false; } else { qDebug() << "OAuth will be used to identify clients using provider at" << _oauthProviderURL.toString(); qDebug() << "OAuth Client ID is" << _oauthClientID; } } return true;} static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id"; void DomainServer::getTemporaryName(bool force) { // check if we already have a domain ID const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); qInfo() << "Requesting temporary domain name"; if (idValueVariant) { qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString(); if (force) { qDebug() << "Requesting temporary domain name to replace current ID:" << getID(); } else { qInfo() << "Abandoning request of temporary domain name."; return; } } // request a temporary name from the metaverse auto accountManager = DependencyManager::get<AccountManager>(); JSONCallbackParameters callbackParameters { this, "handleTempDomainSuccess", this, "handleTempDomainError" }; accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, QNetworkAccessManager::PostOperation, callbackParameters);} void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); // grab the information for the new domain static const QString DATA_KEY = "data"; static const QString DOMAIN_KEY = "domain"; static const QString ID_KEY = "id"; static const QString NAME_KEY = "name"; static const QString KEY_KEY = "api_key"; auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject(); if (!domainObject.isEmpty()) { auto id = domainObject[ID_KEY].toString(); auto name = domainObject[NAME_KEY].toString(); auto key = domainObject[KEY_KEY].toString(); qInfo() << "Received new temporary domain name" << name; qDebug() << "The temporary domain ID is" << id; // store the new domain ID and auto network setting immediately QString newSettingsJSON = QString("{\"metaverse\": { \"id\": \"%1\", \"automatic_networking\": \"full\"}}").arg(id); auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8()); _settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object()); // store the new ID and auto networking setting on disk _settingsManager.persistToFile(); // change our domain ID immediately DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id }); // store the new token to the account info auto accountManager = DependencyManager::get<AccountManager>(); accountManager->setTemporaryDomain(id, key); // update our heartbeats to use the correct id setupICEHeartbeatForFullNetworking(); setupHeartbeatToMetaverse(); } else { qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again" << "via domain-server relaunch or from the domain-server settings."; }} void DomainServer::handleTempDomainError(QNetworkReply& requestReply) { qWarning() << "A temporary name was requested but there was an error creating one. Please try again via domain-server relaunch" << "or from the domain-server settings.";} const QString DOMAIN_CONFIG_ID_KEY = "id"; const QString METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH = "metaverse.automatic_networking";const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full";const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip";const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled"; bool DomainServer::packetVersionMatch(const udt::Packet& packet) { PacketType headerType = NLPacket::typeInHeader(packet); PacketVersion headerVersion = NLPacket::versionInHeader(packet); auto nodeList = DependencyManager::get<LimitedNodeList>(); // if this is a mismatching connect packet, we can't simply drop it on the floor // send back a packet to the interface that tells them we refuse connection for a mismatch if (headerType == PacketType::DomainConnectRequest && headerVersion != versionForPacketType(PacketType::DomainConnectRequest)) { DomainGatekeeper::sendProtocolMismatchConnectionDenial(packet.getSenderSockAddr()); } // let the normal nodeList implementation handle all other packets. return nodeList->isPacketVerified(packet);} void DomainServer::setupNodeListAndAssignments() { const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); unsigned short domainServerPort = (unsigned short) localPortValue.toUInt(); QVariantMap& settingsMap = _settingsManager.getSettingsMap(); unsigned short domainServerDTLSPort = 0; if (_isUsingDTLS) { domainServerDTLSPort = DEFAULT_DOMAIN_SERVER_DTLS_PORT; const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port"; if (settingsMap.contains(CUSTOM_DTLS_PORT_OPTION)) { domainServerDTLSPort = (unsigned short) settingsMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt(); } } QSet<Assignment::Type> parsedTypes; parseAssignmentConfigs(parsedTypes); populateDefaultStaticAssignmentsExcludingTypes(parsedTypes); // check for scripts the user wants to persist from their domain-server config populateStaticScriptedAssignmentsFromSettings(); auto nodeList = DependencyManager::set<LimitedNodeList>(domainServerPort, domainServerDTLSPort); // no matter the local port, save it to shared mem so that local assignment clients can ask what it is nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this, nodeList->getSocketLocalPort()); // store our local http ports in shared memory quint16 localHttpPort = DOMAIN_SERVER_HTTP_PORT; nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_HTTP_PORT_SMEM_KEY, this, localHttpPort); quint16 localHttpsPort = DOMAIN_SERVER_HTTPS_PORT; nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_HTTPS_PORT_SMEM_KEY, this, localHttpsPort); // set our LimitedNodeList UUID to match the UUID from our config // nodes will currently use this to add resources to data-web that relate to our domain const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); if (idValueVariant) { nodeList->setSessionUUID(idValueVariant->toString()); } connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); // register as the packet receiver for the types we want PacketReceiver& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::RequestAssignment, this, "processRequestAssignmentPacket"); packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket"); packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket"); packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket"); packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket"); // NodeList won't be available to the settings manager when it is created, so call registerListener here packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket"); // register the gatekeeper for the packets it needs to receive packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket"); packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket"); packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket"); packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket"); packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK"); // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); // set a custum packetVersionMatch as the verify packet operator for the udt::Socket nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch);} const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; bool DomainServer::resetAccountManagerAccessToken() { if (!_oauthProviderURL.isEmpty()) { // check for an access-token in our settings, can optionally be overidden by env value const QString ENV_ACCESS_TOKEN_KEY = "DOMAIN_SERVER_ACCESS_TOKEN"; QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY); if (accessToken.isEmpty()) { const QVariant* accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) { accessToken = accessTokenVariant->toString(); } else { qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."; qDebug() << "Set an access token via the web interface, in your user or master config" << "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN"; // clear any existing access token from AccountManager DependencyManager::get<AccountManager>()->setAccessTokenForCurrentAuthURL(QString()); return false; } } else { qDebug() << "Using access token from DOMAIN_SERVER_ACCESS_TOKEN in env. This overrides any access token present" << " in the user or master config."; } // give this access token to the AccountManager DependencyManager::get<AccountManager>()->setAccessTokenForCurrentAuthURL(accessToken); return true; } else { static const QString MISSING_OAUTH_PROVIDER_MSG = QString("Missing OAuth provider URL, but a domain-server feature was required that requires authentication.") + QString("domain-server will now quit."); static const int MISSING_OAUTH_PROVIDER_ERROR_CODE = 5; QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection, Q_ARG(QString, MISSING_OAUTH_PROVIDER_MSG), Q_ARG(int, MISSING_OAUTH_PROVIDER_ERROR_CODE)); return false; }} void DomainServer::setupAutomaticNetworking() { qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; resetAccountManagerAccessToken(); _automaticNetworkingSetting = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); auto nodeList = DependencyManager::get<LimitedNodeList>(); const QUuid& domainID = getID(); if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { setupICEHeartbeatForFullNetworking(); } if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE || _automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { if (!domainID.isNull()) { qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID" << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) { // send any public socket changes to the data server so nodes can find us at our new IP connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate); // have the LNL enable public socket updating via STUN nodeList->startSTUNPublicSocketUpdate(); }Lines 1–500 of 2325. Load more
// // DomainServer.cpp // domain-server/src // // Created by Stephen Birarda on 9/26/13. // Copyright 2013 High Fidelity, Inc. // // 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 <QDir> #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> #include <QProcess> #include <QSharedMemory> #include <QStandardPaths> #include <QTimer> #include <QUrlQuery> #include <AccountManager.h> #include <BuildInfo.h> #include <DependencyManager.h> #include <HifiConfigVariantMap.h> #include <HTTPConnection.h> #include <LogUtils.h> #include <NetworkingConstants.h> #include <udt/PacketHeaders.h> #include <SettingHandle.h> #include <SharedUtil.h> #include <ShutdownEventListener.h> #include <UUID.h> #include <LogHandler.h> #include <ServerPathUtils.h> #include "DomainServerNodeData.h" #include "NodeConnectionData.h" int const DomainServer::EXIT_CODE_REBOOT = 234923; const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com"; 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), _httpsManager(NULL), _allAssignments(), _unfulfilledAssignments(), _isUsingDTLS(false), _oauthProviderURL(), _oauthClientID(), _hostname(), _ephemeralACScripts(), _webAuthenticationStateSet(), _cookieSessionHash(), _automaticNetworkingSetting(), _settingsManager() { qInstallMessageHandler(LogHandler::verboseMessageHandler); LogUtils::init(); Setting::init(); connect(this, &QCoreApplication::aboutToQuit, this, &DomainServer::aboutToQuit); setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION); setOrganizationDomain("highfidelity.io"); setApplicationName("domain-server"); setApplicationVersion(BuildInfo::VERSION); QSettings::setDefaultFormat(QSettings::IniFormat); qDebug() << "Setting up domain-server"; // make sure we have a fresh AccountManager instance // (need this since domain-server can restart itself and maintain static variables) DependencyManager::set<AccountManager>(); auto args = arguments(); _settingsManager.setupConfigMap(args); // setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us #ifdef _WIN32 installNativeEventFilter(&ShutdownEventListener::getInstance()); #else ShutdownEventListener::getInstance(); #endif 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); // if we were given a certificate/private key or oauth credentials they must succeed if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) { return; } setupNodeListAndAssignments(); setupAutomaticNetworking(); if (!getID().isNull()) { setupHeartbeatToMetaverse(); // send the first heartbeat immediately sendHeartbeatToMetaverse(); } // check for the temporary name parameter const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name"; if (args.contains(GET_TEMPORARY_NAME_SWITCH)) { getTemporaryName(); } _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request _metadata = new DomainMetadata(this); qDebug() << "domain-server is running"; } DomainServer::~DomainServer() { // destroy the LimitedNodeList before the DomainServer QCoreApplication is down DependencyManager::destroy<LimitedNodeList>(); } void DomainServer::queuedQuit(QString quitMessage, int exitCode) { if (!quitMessage.isEmpty()) { qCritical() << qPrintable(quitMessage); } QCoreApplication::exit(exitCode); } void DomainServer::aboutToQuit() { // clear the log handler so that Qt doesn't call the destructor on LogHandler qInstallMessageHandler(0); } void DomainServer::restart() { qDebug() << "domain-server is restarting."; exit(DomainServer::EXIT_CODE_REBOOT); } const QUuid& DomainServer::getID() { return DependencyManager::get<LimitedNodeList>()->getSessionUUID(); } bool DomainServer::optionallyReadX509KeyAndCertificate() { const QString X509_CERTIFICATE_OPTION = "cert"; const QString X509_PRIVATE_KEY_OPTION = "key"; const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; QString certPath = _settingsManager.getSettingsMap().value(X509_CERTIFICATE_OPTION).toString(); QString keyPath = _settingsManager.getSettingsMap().value(X509_PRIVATE_KEY_OPTION).toString(); if (!certPath.isEmpty() && !keyPath.isEmpty()) { // the user wants to use the following cert and key for HTTPS // this is used for Oauth callbacks when authorizing users against a data server // let's make sure we can load the key and certificate QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV); qDebug() << "Reading certificate file at" << certPath << "for HTTPS."; qDebug() << "Reading key file at" << keyPath << "for HTTPS."; QFile certFile(certPath); certFile.open(QIODevice::ReadOnly); QFile keyFile(keyPath); keyFile.open(QIODevice::ReadOnly); QSslCertificate sslCertificate(&certFile); QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8()); _httpsManager = new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this, this); qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT; } else if (!certPath.isEmpty() || !keyPath.isEmpty()) { static const QString MISSING_CERT_ERROR_MSG = "Missing certificate or private key. domain-server will now quit."; static const int MISSING_CERT_ERROR_CODE = 3; QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection, Q_ARG(QString, MISSING_CERT_ERROR_MSG), Q_ARG(int, MISSING_CERT_ERROR_CODE)); return false; } return true; } bool DomainServer::optionallySetupOAuth() { const QString OAUTH_PROVIDER_URL_OPTION = "oauth-provider"; const QString OAUTH_CLIENT_ID_OPTION = "oauth-client-id"; const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET"; const QString REDIRECT_HOSTNAME_OPTION = "hostname"; const QVariantMap& settingsMap = _settingsManager.getSettingsMap(); _oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString()); // if we don't have an oauth provider URL then we default to the default node auth url if (_oauthProviderURL.isEmpty()) { _oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL; } auto accountManager = DependencyManager::get<AccountManager>(); accountManager->setAuthURL(_oauthProviderURL); _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString(); _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV); _hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString(); if (!_oauthClientID.isEmpty()) { if (_oauthProviderURL.isEmpty() || _hostname.isEmpty() || _oauthClientID.isEmpty() || _oauthClientSecret.isEmpty()) { static const QString MISSING_OAUTH_INFO_MSG = "Missing OAuth provider URL, hostname, client ID, or client secret. domain-server will now quit."; static const int MISSING_OAUTH_INFO_ERROR_CODE = 4; QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection, Q_ARG(QString, MISSING_OAUTH_INFO_MSG), Q_ARG(int, MISSING_OAUTH_INFO_ERROR_CODE)); return false; } else { qDebug() << "OAuth will be used to identify clients using provider at" << _oauthProviderURL.toString(); qDebug() << "OAuth Client ID is" << _oauthClientID; } } return true; } static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id"; void DomainServer::getTemporaryName(bool force) { // check if we already have a domain ID const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); qInfo() << "Requesting temporary domain name"; if (idValueVariant) { qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString(); if (force) { qDebug() << "Requesting temporary domain name to replace current ID:" << getID(); } else { qInfo() << "Abandoning request of temporary domain name."; return; } } // request a temporary name from the metaverse auto accountManager = DependencyManager::get<AccountManager>(); JSONCallbackParameters callbackParameters { this, "handleTempDomainSuccess", this, "handleTempDomainError" }; accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, QNetworkAccessManager::PostOperation, callbackParameters); } void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); // grab the information for the new domain static const QString DATA_KEY = "data"; static const QString DOMAIN_KEY = "domain"; static const QString ID_KEY = "id"; static const QString NAME_KEY = "name"; static const QString KEY_KEY = "api_key"; auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject(); if (!domainObject.isEmpty()) { auto id = domainObject[ID_KEY].toString(); auto name = domainObject[NAME_KEY].toString(); auto key = domainObject[KEY_KEY].toString(); qInfo() << "Received new temporary domain name" << name; qDebug() << "The temporary domain ID is" << id; // store the new domain ID and auto network setting immediately QString newSettingsJSON = QString("{\"metaverse\": { \"id\": \"%1\", \"automatic_networking\": \"full\"}}").arg(id); auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8()); _settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object()); // store the new ID and auto networking setting on disk _settingsManager.persistToFile(); // change our domain ID immediately DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id }); // store the new token to the account info auto accountManager = DependencyManager::get<AccountManager>(); accountManager->setTemporaryDomain(id, key); // update our heartbeats to use the correct id setupICEHeartbeatForFullNetworking(); setupHeartbeatToMetaverse(); } else { qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again" << "via domain-server relaunch or from the domain-server settings."; } } void DomainServer::handleTempDomainError(QNetworkReply& requestReply) { qWarning() << "A temporary name was requested but there was an error creating one. Please try again via domain-server relaunch" << "or from the domain-server settings."; } const QString DOMAIN_CONFIG_ID_KEY = "id"; const QString METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH = "metaverse.automatic_networking"; const QString FULL_AUTOMATIC_NETWORKING_VALUE = "full"; const QString IP_ONLY_AUTOMATIC_NETWORKING_VALUE = "ip"; const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled"; bool DomainServer::packetVersionMatch(const udt::Packet& packet) { PacketType headerType = NLPacket::typeInHeader(packet); PacketVersion headerVersion = NLPacket::versionInHeader(packet); auto nodeList = DependencyManager::get<LimitedNodeList>(); // if this is a mismatching connect packet, we can't simply drop it on the floor // send back a packet to the interface that tells them we refuse connection for a mismatch if (headerType == PacketType::DomainConnectRequest && headerVersion != versionForPacketType(PacketType::DomainConnectRequest)) { DomainGatekeeper::sendProtocolMismatchConnectionDenial(packet.getSenderSockAddr()); } // let the normal nodeList implementation handle all other packets. return nodeList->isPacketVerified(packet); } void DomainServer::setupNodeListAndAssignments() { const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); unsigned short domainServerPort = (unsigned short) localPortValue.toUInt(); QVariantMap& settingsMap = _settingsManager.getSettingsMap(); unsigned short domainServerDTLSPort = 0; if (_isUsingDTLS) { domainServerDTLSPort = DEFAULT_DOMAIN_SERVER_DTLS_PORT; const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port"; if (settingsMap.contains(CUSTOM_DTLS_PORT_OPTION)) { domainServerDTLSPort = (unsigned short) settingsMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt(); } } QSet<Assignment::Type> parsedTypes; parseAssignmentConfigs(parsedTypes); populateDefaultStaticAssignmentsExcludingTypes(parsedTypes); // check for scripts the user wants to persist from their domain-server config populateStaticScriptedAssignmentsFromSettings(); auto nodeList = DependencyManager::set<LimitedNodeList>(domainServerPort, domainServerDTLSPort); // no matter the local port, save it to shared mem so that local assignment clients can ask what it is nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this, nodeList->getSocketLocalPort()); // store our local http ports in shared memory quint16 localHttpPort = DOMAIN_SERVER_HTTP_PORT; nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_HTTP_PORT_SMEM_KEY, this, localHttpPort); quint16 localHttpsPort = DOMAIN_SERVER_HTTPS_PORT; nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_HTTPS_PORT_SMEM_KEY, this, localHttpsPort); // set our LimitedNodeList UUID to match the UUID from our config // nodes will currently use this to add resources to data-web that relate to our domain const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); if (idValueVariant) { nodeList->setSessionUUID(idValueVariant->toString()); } connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); // register as the packet receiver for the types we want PacketReceiver& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::RequestAssignment, this, "processRequestAssignmentPacket"); packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket"); packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket"); packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket"); packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket"); // NodeList won't be available to the settings manager when it is created, so call registerListener here packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket"); // register the gatekeeper for the packets it needs to receive packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket"); packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket"); packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket"); packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket"); packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket"); packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK"); // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); // set a custum packetVersionMatch as the verify packet operator for the udt::Socket nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch); } const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; bool DomainServer::resetAccountManagerAccessToken() { if (!_oauthProviderURL.isEmpty()) { // check for an access-token in our settings, can optionally be overidden by env value const QString ENV_ACCESS_TOKEN_KEY = "DOMAIN_SERVER_ACCESS_TOKEN"; QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY); if (accessToken.isEmpty()) { const QVariant* accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) { accessToken = accessTokenVariant->toString(); } else { qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."; qDebug() << "Set an access token via the web interface, in your user or master config" << "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN"; // clear any existing access token from AccountManager DependencyManager::get<AccountManager>()->setAccessTokenForCurrentAuthURL(QString()); return false; } } else { qDebug() << "Using access token from DOMAIN_SERVER_ACCESS_TOKEN in env. This overrides any access token present" << " in the user or master config."; } // give this access token to the AccountManager DependencyManager::get<AccountManager>()->setAccessTokenForCurrentAuthURL(accessToken); return true; } else { static const QString MISSING_OAUTH_PROVIDER_MSG = QString("Missing OAuth provider URL, but a domain-server feature was required that requires authentication.") + QString("domain-server will now quit."); static const int MISSING_OAUTH_PROVIDER_ERROR_CODE = 5; QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection, Q_ARG(QString, MISSING_OAUTH_PROVIDER_MSG), Q_ARG(int, MISSING_OAUTH_PROVIDER_ERROR_CODE)); return false; } } void DomainServer::setupAutomaticNetworking() { qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting; resetAccountManagerAccessToken(); _automaticNetworkingSetting = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); auto nodeList = DependencyManager::get<LimitedNodeList>(); const QUuid& domainID = getID(); if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { setupICEHeartbeatForFullNetworking(); } if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE || _automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { if (!domainID.isNull()) { qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID" << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) { // send any public socket changes to the data server so nodes can find us at our new IP connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate); // have the LNL enable public socket updating via STUN nodeList->startSTUNPublicSocketUpdate(); }