2325 lines · 99.9 KB · C++
RawDownload
////  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.