mumble/src/murmur/Meta.cpp
Robert Adam 27dbee8e62 FEAT(client): Plugin framework
This commit introduces a new plugin framework into the codebase of the
Mumble client. Note that "plugin" here really refers to a (more or less)
general purpose plugin and is therefore not to be confused with the
previously available positional data plugins (only responsible for
fetching positional data from a running game and passing that to
Mumble).

The plugin interface is written in C, removing the compiler-dependence
the old "plugins" had. Instead plugins can now be written in an
arbitrary language as long as that language is capable of being compiled
into a shared library and also being capable of being C-compatible.

As already indicated a plugin is essentially a shared library that
provides certain functions that allow Mumble to interface with it.

Inside Mumble the so-called PluginManager is responsible for managing
the plugins and relaying events to the respective callbacks. Plugins
themselves can also interact with Mumble on their own initiative by
using the provided API functions.

Fixes #2455
Fixes #2148
Fixes #1594
Fixes #2051
Fixes #3742
Fixes #4575
Fixes #4751
2021-04-16 20:15:44 +02:00

829 lines
28 KiB
C++

// Copyright 2007-2021 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "Meta.h"
#include "Connection.h"
#include "EnvUtils.h"
#include "FFDHE.h"
#include "Net.h"
#include "OSInfo.h"
#include "SSL.h"
#include "Server.h"
#include "ServerDB.h"
#include "Version.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QSettings>
#ifdef Q_OS_WIN
# include <QtCore/QStandardPaths>
#endif
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QNetworkInterface>
#if defined(USE_QSSLDIFFIEHELLMANPARAMETERS)
# include <QtNetwork/QSslDiffieHellmanParameters>
#endif
#ifdef Q_OS_WIN
# include <qos2.h>
#else
# include <pwd.h>
# include <sys/resource.h>
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
# include <QRandomGenerator>
#endif
MetaParams Meta::mp;
#ifdef Q_OS_WIN
HANDLE Meta::hQoS = nullptr;
#endif
MetaParams::MetaParams() {
qsPassword = QString();
usPort = DEFAULT_MUMBLE_PORT;
iTimeout = 30;
// This represents the maximum possible bandwidth using 10 ms audio TCP packets with position data
// (restricted by the maximum bitrate Opus supports)
// 558000 = 510000 (Opus) + 9600 (position) + 38400 (TCP overhead)
iMaxBandwidth = 558000;
iMaxUsers = 1000;
iMaxUsersPerChannel = 0;
iMaxListenersPerChannel = -1;
iMaxListenerProxiesPerUser = -1;
iMaxTextMessageLength = 5000;
iMaxImageMessageLength = 131072;
legacyPasswordHash = false;
kdfIterations = -1;
bAllowHTML = true;
iDefaultChan = 0;
bRememberChan = true;
iRememberChanDuration = 0;
qsWelcomeText = QString();
qsWelcomeTextFile = QString();
qsDatabase = QString();
iSQLiteWAL = 0;
iDBPort = 0;
qsDBusService = "net.sourceforge.mumble.murmur";
qsDBDriver = "QSQLITE";
qsLogfile = "murmur.log";
iLogDays = 31;
iObfuscate = 0;
bSendVersion = true;
bBonjour = true;
bAllowPing = true;
bCertRequired = false;
bForceExternalAuth = false;
iBanTries = 10;
iBanTimeframe = 120;
iBanTime = 300;
bBanSuccessful = true;
#ifdef Q_OS_UNIX
uiUid = uiGid = 0;
#endif
iOpusThreshold = 100;
iChannelNestingLimit = 10;
iChannelCountLimit = 1000;
qrUserName = QRegExp(QLatin1String("[ -=\\w\\[\\]\\{\\}\\(\\)\\@\\|\\.]+"));
qrChannelName = QRegExp(QLatin1String("[ -=\\w\\#\\[\\]\\{\\}\\(\\)\\@\\|]+"));
iMessageLimit = 1;
iMessageBurst = 5;
iPluginMessageLimit = 4;
iPluginMessageBurst = 15;
qsCiphers = MumbleSSL::defaultOpenSSLCipherString();
bLogGroupChanges = false;
bLogACLChanges = false;
qsSettings = nullptr;
}
MetaParams::~MetaParams() {
delete qsSettings;
}
/**
* Checks whether a qsSettings config variable named 'name' was set to a valid value for the type
* we want to convert it to. Otherwise a error message is logged and 'defaultValue' is returned.
*
* E.g. checkedFromSettings<QString>("blub", bla) would output an error message and leave bla unchanged
* if the user had set the ini parameter to "blub = has, commas, in, it" which QSettings will interpret
* not as a String but as a list of strings.
*
* @param T Conversion target type (type of 'defaultValue', auto inducable)
* @param name qsSettings variable name
* @param defaultValue Default value for 'name'
* @param settings The QSettings object to read from. If null, the Meta's qsSettings will be used.
* @return Setting if valid, default if not or setting not set.
*/
template< class T >
T MetaParams::typeCheckedFromSettings(const QString &name, const T &defaultValue, QSettings *settings) {
// Use qsSettings unless a specific QSettings instance
// is requested.
if (!settings) {
settings = qsSettings;
}
QVariant cfgVariable = settings->value(name, defaultValue);
if (!cfgVariable.convert(
QVariant(defaultValue)
.type())) { // Bit convoluted as canConvert<T>() only does a static check without considering whether
// say a string like "blub" is actually a valid double (which convert does).
qCritical() << "Configuration variable" << name << "is of invalid format. Set to default value of"
<< defaultValue << ".";
return defaultValue;
}
return cfgVariable.value< T >();
}
void MetaParams::read(QString fname) {
qmConfig.clear();
if (fname.isEmpty()) {
QStringList datapaths;
#if defined(Q_OS_WIN)
datapaths << QStandardPaths::writableLocation(QStandardPaths::DataLocation);
QDir appdir = QDir(QDir::fromNativeSeparators(EnvUtils::getenv(QLatin1String("APPDATA"))));
datapaths << appdir.absolutePath() + QLatin1String("/Mumble");
#else
datapaths << QDir::homePath() + QLatin1String("/.murmurd");
datapaths << QDir::homePath() + QLatin1String("/.config/Mumble");
#endif
#if defined(Q_OS_MAC)
/* Old Mac data path. */
datapaths << QDir::homePath() + QLatin1String("/Library/Preferences/Mumble/");
#endif
datapaths << QDir::homePath();
datapaths << QDir::currentPath();
datapaths << QCoreApplication::instance()->applicationDirPath();
foreach (const QString &p, datapaths) {
if (!p.isEmpty()) {
QFileInfo fi(p, "murmur.ini");
if (fi.exists() && fi.isReadable()) {
qdBasePath = QDir(p);
qsAbsSettingsFilePath = fi.absoluteFilePath();
break;
}
}
}
if (qsAbsSettingsFilePath.isEmpty()) {
QDir::root().mkpath(qdBasePath.absolutePath());
qdBasePath = QDir(datapaths.at(0));
qsAbsSettingsFilePath = qdBasePath.absolutePath() + QLatin1String("/murmur.ini");
}
} else {
QFile f(fname);
if (!f.open(QIODevice::ReadOnly)) {
qFatal("Specified ini file %s could not be opened", qPrintable(fname));
}
qdBasePath = QFileInfo(f).absoluteDir();
qsAbsSettingsFilePath = QFileInfo(f).absoluteFilePath();
f.close();
}
QDir::setCurrent(qdBasePath.absolutePath());
qsSettings = new QSettings(qsAbsSettingsFilePath, QSettings::IniFormat);
qsSettings->setIniCodec("UTF-8");
qWarning("Initializing settings from %s (basepath %s)", qPrintable(qsSettings->fileName()),
qPrintable(qdBasePath.absolutePath()));
QString qsHost = qsSettings->value("host", QString()).toString();
if (!qsHost.isEmpty()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
foreach (const QString &host, qsHost.split(QRegExp(QLatin1String("\\s+")), Qt::SkipEmptyParts)) {
#else
// Qt 5.14 introduced the Qt::SplitBehavior flags deprecating the QString fields
foreach (const QString &host, qsHost.split(QRegExp(QLatin1String("\\s+")), QString::SkipEmptyParts)) {
#endif
QHostAddress qhaddr;
if (qhaddr.setAddress(host)) {
qlBind << qhaddr;
} else {
bool found = false;
QHostInfo hi = QHostInfo::fromName(host);
foreach (QHostAddress qha, hi.addresses()) {
if ((qha.protocol() == QAbstractSocket::IPv4Protocol)
|| (qha.protocol() == QAbstractSocket::IPv6Protocol)) {
qlBind << qha;
found = true;
}
}
if (!found) {
qFatal("Lookup of bind hostname %s failed", qPrintable(host));
}
}
}
foreach (const QHostAddress &qha, qlBind)
qWarning("Binding to address %s", qPrintable(qha.toString()));
}
if (qlBind.isEmpty()) {
bool hasipv6 = false;
bool hasipv4 = false;
int nif = 0;
QList< QNetworkInterface > interfaces = QNetworkInterface::allInterfaces();
if (interfaces.isEmpty()) {
qWarning("Meta: Unable to acquire list of network interfaces.");
} else {
foreach (const QNetworkInterface &qni, interfaces) {
if (!(qni.flags() & QNetworkInterface::IsUp))
continue;
if (!(qni.flags() & QNetworkInterface::IsRunning))
continue;
if (qni.flags() & QNetworkInterface::IsLoopBack)
continue;
foreach (const QNetworkAddressEntry &qna, qni.addressEntries()) {
const QHostAddress &qha = qna.ip();
switch (qha.protocol()) {
case QAbstractSocket::IPv4Protocol:
hasipv4 = true;
break;
case QAbstractSocket::IPv6Protocol:
hasipv6 = true;
break;
default:
break;
}
}
++nif;
}
}
if (nif == 0) {
qWarning("Meta: Could not determine IPv4/IPv6 support via network interfaces, assuming support for both.");
hasipv6 = true;
hasipv4 = true;
}
if (hasipv6) {
if (SslServer::hasDualStackSupport() && hasipv4) {
qlBind << QHostAddress(QHostAddress::Any);
hasipv4 = false; // No need to add a separate ipv4 socket
} else {
qlBind << QHostAddress(QHostAddress::AnyIPv6);
}
}
if (hasipv4) {
qlBind << QHostAddress(QHostAddress::AnyIPv4);
}
}
qsPassword = typeCheckedFromSettings("serverpassword", qsPassword);
usPort = static_cast< unsigned short >(typeCheckedFromSettings("port", static_cast< uint >(usPort)));
iTimeout = typeCheckedFromSettings("timeout", iTimeout);
iMaxTextMessageLength = typeCheckedFromSettings("textmessagelength", iMaxTextMessageLength);
iMaxImageMessageLength = typeCheckedFromSettings("imagemessagelength", iMaxImageMessageLength);
legacyPasswordHash = typeCheckedFromSettings("legacypasswordhash", legacyPasswordHash);
kdfIterations = typeCheckedFromSettings("kdfiterations", -1);
bAllowHTML = typeCheckedFromSettings("allowhtml", bAllowHTML);
iMaxBandwidth = typeCheckedFromSettings("bandwidth", iMaxBandwidth);
iDefaultChan = typeCheckedFromSettings("defaultchannel", iDefaultChan);
bRememberChan = typeCheckedFromSettings("rememberchannel", bRememberChan);
iRememberChanDuration = typeCheckedFromSettings("rememberchannelduration", iRememberChanDuration);
iMaxUsers = typeCheckedFromSettings("users", iMaxUsers);
iMaxUsersPerChannel = typeCheckedFromSettings("usersperchannel", iMaxUsersPerChannel);
iMaxListenersPerChannel = typeCheckedFromSettings("listenersperchannel", iMaxListenersPerChannel);
iMaxListenerProxiesPerUser = typeCheckedFromSettings("listenersperuser", iMaxListenerProxiesPerUser);
qsWelcomeText = typeCheckedFromSettings("welcometext", qsWelcomeText);
qsWelcomeTextFile = typeCheckedFromSettings("welcometextfile", qsWelcomeTextFile);
bCertRequired = typeCheckedFromSettings("certrequired", bCertRequired);
bForceExternalAuth = typeCheckedFromSettings("forceExternalAuth", bForceExternalAuth);
qsDatabase = typeCheckedFromSettings("database", qsDatabase);
iSQLiteWAL = typeCheckedFromSettings("sqlite_wal", iSQLiteWAL);
qsDBDriver = typeCheckedFromSettings("dbDriver", qsDBDriver);
qsDBUserName = typeCheckedFromSettings("dbUsername", qsDBUserName);
qsDBPassword = typeCheckedFromSettings("dbPassword", qsDBPassword);
qsDBHostName = typeCheckedFromSettings("dbHost", qsDBHostName);
qsDBPrefix = typeCheckedFromSettings("dbPrefix", qsDBPrefix);
qsDBOpts = typeCheckedFromSettings("dbOpts", qsDBOpts);
iDBPort = typeCheckedFromSettings("dbPort", iDBPort);
qsIceEndpoint = typeCheckedFromSettings("ice", qsIceEndpoint);
qsIceSecretRead = typeCheckedFromSettings("icesecret", qsIceSecretRead);
qsIceSecretRead = typeCheckedFromSettings("icesecretread", qsIceSecretRead);
qsIceSecretWrite = typeCheckedFromSettings("icesecretwrite", qsIceSecretRead);
qsGRPCAddress = typeCheckedFromSettings("grpc", qsGRPCAddress);
qsGRPCCert = typeCheckedFromSettings("grpccert", qsGRPCCert);
qsGRPCKey = typeCheckedFromSettings("grpckey", qsGRPCKey);
qsGRPCAuthorized = typeCheckedFromSettings("grpcauthorized", qsGRPCAuthorized);
iLogDays = typeCheckedFromSettings("logdays", iLogDays);
qsDBus = typeCheckedFromSettings("dbus", qsDBus);
qsDBusService = typeCheckedFromSettings("dbusservice", qsDBusService);
qsLogfile = typeCheckedFromSettings("logfile", qsLogfile);
qsPid = typeCheckedFromSettings("pidfile", qsPid);
qsRegName = typeCheckedFromSettings("registerName", qsRegName);
qsRegPassword = typeCheckedFromSettings("registerPassword", qsRegPassword);
qsRegHost = typeCheckedFromSettings("registerHostname", qsRegHost);
qsRegLocation = typeCheckedFromSettings("registerLocation", qsRegLocation);
qurlRegWeb = QUrl(typeCheckedFromSettings("registerUrl", qurlRegWeb).toString());
bBonjour = typeCheckedFromSettings("bonjour", bBonjour);
iBanTries = typeCheckedFromSettings("autobanAttempts", iBanTries);
iBanTimeframe = typeCheckedFromSettings("autobanTimeframe", iBanTimeframe);
iBanTime = typeCheckedFromSettings("autobanTime", iBanTime);
bBanSuccessful = typeCheckedFromSettings("autobanSuccessfulConnections", bBanSuccessful);
qvSuggestVersion = MumbleVersion::getRaw(qsSettings->value("suggestVersion").toString());
if (qvSuggestVersion.toUInt() == 0)
qvSuggestVersion = QVariant();
qvSuggestPositional = qsSettings->value("suggestPositional");
if (qvSuggestPositional.toString().trimmed().isEmpty())
qvSuggestPositional = QVariant();
qvSuggestPushToTalk = qsSettings->value("suggestPushToTalk");
if (qvSuggestPushToTalk.toString().trimmed().isEmpty())
qvSuggestPushToTalk = QVariant();
bLogGroupChanges = typeCheckedFromSettings("loggroupchanges", bLogGroupChanges);
bLogACLChanges = typeCheckedFromSettings("logaclchanges", bLogACLChanges);
iOpusThreshold = typeCheckedFromSettings("opusthreshold", iOpusThreshold);
iChannelNestingLimit = typeCheckedFromSettings("channelnestinglimit", iChannelNestingLimit);
iChannelCountLimit = typeCheckedFromSettings("channelcountlimit", iChannelCountLimit);
#ifdef Q_OS_UNIX
qsName = qsSettings->value("uname").toString();
if (geteuid() == 0) {
// TODO: remove this silent fallback to enforce running as non-root
bool requested = true;
if (qsName.isEmpty()) {
// default server user name
qsName = "mumble-server";
requested = false;
}
struct passwd *pw = getpwnam(qPrintable(qsName));
if (pw) {
uiUid = pw->pw_uid;
uiGid = pw->pw_gid;
qsHome = QString::fromUtf8(pw->pw_dir);
} else if (requested) {
qFatal("Cannot find username %s", qPrintable(qsName));
}
endpwent();
}
#endif
qrUserName = QRegExp(typeCheckedFromSettings("username", qrUserName.pattern()));
qrChannelName = QRegExp(typeCheckedFromSettings("channelname", qrChannelName.pattern()));
iMessageLimit = typeCheckedFromSettings("messagelimit", 1);
iMessageBurst = typeCheckedFromSettings("messageburst", 5);
iPluginMessageLimit = typeCheckedFromSettings("pluginmessagelimit", 4);
iPluginMessageBurst = typeCheckedFromSettings("pluginmessageburst", 15);
bool bObfuscate = typeCheckedFromSettings("obfuscate", false);
if (bObfuscate) {
qWarning("IP address obfuscation enabled.");
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
iObfuscate = QRandomGenerator::global()->generate();
#else
// Qt 5.10 introduces the QRandomGenerator class and in Qt 5.15 qrand got deprecated in its favor
iObfuscate = qrand();
#endif
}
bSendVersion = typeCheckedFromSettings("sendversion", bSendVersion);
bAllowPing = typeCheckedFromSettings("allowping", bAllowPing);
if (!loadSSLSettings()) {
qFatal("MetaParams: Failed to load SSL settings. See previous errors.");
}
QStringList hosts;
foreach (const QHostAddress &qha, qlBind) { hosts << qha.toString(); }
qmConfig.insert(QLatin1String("host"), hosts.join(" "));
qmConfig.insert(QLatin1String("password"), qsPassword);
qmConfig.insert(QLatin1String("port"), QString::number(usPort));
qmConfig.insert(QLatin1String("timeout"), QString::number(iTimeout));
qmConfig.insert(QLatin1String("textmessagelength"), QString::number(iMaxTextMessageLength));
qmConfig.insert(QLatin1String("legacypasswordhash"),
legacyPasswordHash ? QLatin1String("true") : QLatin1String("false"));
qmConfig.insert(QLatin1String("kdfiterations"), QString::number(kdfIterations));
qmConfig.insert(QLatin1String("allowhtml"), bAllowHTML ? QLatin1String("true") : QLatin1String("false"));
qmConfig.insert(QLatin1String("bandwidth"), QString::number(iMaxBandwidth));
qmConfig.insert(QLatin1String("users"), QString::number(iMaxUsers));
qmConfig.insert(QLatin1String("defaultchannel"), QString::number(iDefaultChan));
qmConfig.insert(QLatin1String("rememberchannel"), bRememberChan ? QLatin1String("true") : QLatin1String("false"));
qmConfig.insert(QLatin1String("rememberchannelduration"), QString::number(iRememberChanDuration));
qmConfig.insert(QLatin1String("welcometext"), qsWelcomeText);
qmConfig.insert(QLatin1String("welcometextfile"), qsWelcomeTextFile);
qmConfig.insert(QLatin1String("registername"), qsRegName);
qmConfig.insert(QLatin1String("registerpassword"), qsRegPassword);
qmConfig.insert(QLatin1String("registerhostname"), qsRegHost);
qmConfig.insert(QLatin1String("registerlocation"), qsRegLocation);
qmConfig.insert(QLatin1String("registerurl"), qurlRegWeb.toString());
qmConfig.insert(QLatin1String("bonjour"), bBonjour ? QLatin1String("true") : QLatin1String("false"));
qmConfig.insert(QLatin1String("certificate"), QString::fromUtf8(qscCert.toPem()));
qmConfig.insert(QLatin1String("key"), QString::fromUtf8(qskKey.toPem()));
qmConfig.insert(QLatin1String("obfuscate"), bObfuscate ? QLatin1String("true") : QLatin1String("false"));
qmConfig.insert(QLatin1String("username"), qrUserName.pattern());
qmConfig.insert(QLatin1String("channelname"), qrChannelName.pattern());
qmConfig.insert(QLatin1String("certrequired"), bCertRequired ? QLatin1String("true") : QLatin1String("false"));
qmConfig.insert(QLatin1String("forceExternalAuth"),
bForceExternalAuth ? QLatin1String("true") : QLatin1String("false"));
qmConfig.insert(QLatin1String("suggestversion"),
qvSuggestVersion.isNull() ? QString() : qvSuggestVersion.toString());
qmConfig.insert(QLatin1String("suggestpositional"),
qvSuggestPositional.isNull() ? QString() : qvSuggestPositional.toString());
qmConfig.insert(QLatin1String("suggestpushtotalk"),
qvSuggestPushToTalk.isNull() ? QString() : qvSuggestPushToTalk.toString());
qmConfig.insert(QLatin1String("opusthreshold"), QString::number(iOpusThreshold));
qmConfig.insert(QLatin1String("channelnestinglimit"), QString::number(iChannelNestingLimit));
qmConfig.insert(QLatin1String("channelcountlimit"), QString::number(iChannelCountLimit));
qmConfig.insert(QLatin1String("sslCiphers"), qsCiphers);
qmConfig.insert(QLatin1String("sslDHParams"), QString::fromLatin1(qbaDHParams.constData()));
}
bool MetaParams::loadSSLSettings() {
QSettings updatedSettings(qsAbsSettingsFilePath, QSettings::IniFormat);
updatedSettings.setIniCodec("UTF-8");
QString tmpCiphersStr = typeCheckedFromSettings("sslCiphers", qsCiphers);
QString qsSSLCert = qsSettings->value("sslCert").toString();
QString qsSSLKey = qsSettings->value("sslKey").toString();
QString qsSSLCA = qsSettings->value("sslCA").toString();
QString qsSSLDHParams = typeCheckedFromSettings(QLatin1String("sslDHParams"), QString(QLatin1String("@ffdhe2048")));
qbaPassPhrase = qsSettings->value("sslPassPhrase").toByteArray();
QSslCertificate tmpCert;
QList< QSslCertificate > tmpCA;
QList< QSslCertificate > tmpIntermediates;
QSslKey tmpKey;
QByteArray tmpDHParams;
QList< QSslCipher > tmpCiphers;
if (!qsSSLCA.isEmpty()) {
QFile pem(qsSSLCA);
if (pem.open(QIODevice::ReadOnly)) {
QByteArray qba = pem.readAll();
pem.close();
QList< QSslCertificate > ql = QSslCertificate::fromData(qba);
if (ql.isEmpty()) {
qCritical("MetaParams: Failed to parse any CA certificates from %s", qPrintable(qsSSLCA));
return false;
} else {
tmpCA = ql;
}
} else {
qCritical("MetaParams: Failed to read %s", qPrintable(qsSSLCA));
return false;
}
}
QByteArray crt, key;
if (!qsSSLCert.isEmpty()) {
QFile pem(qsSSLCert);
if (pem.open(QIODevice::ReadOnly)) {
crt = pem.readAll();
pem.close();
} else {
qCritical("MetaParams: Failed to read %s", qPrintable(qsSSLCert));
return false;
}
}
if (!qsSSLKey.isEmpty()) {
QFile pem(qsSSLKey);
if (pem.open(QIODevice::ReadOnly)) {
key = pem.readAll();
pem.close();
} else {
qCritical("MetaParams: Failed to read %s", qPrintable(qsSSLKey));
return false;
}
}
if (!key.isEmpty() || !crt.isEmpty()) {
if (!key.isEmpty()) {
tmpKey = Server::privateKeyFromPEM(key, qbaPassPhrase);
}
if (tmpKey.isNull() && !crt.isEmpty()) {
tmpKey = Server::privateKeyFromPEM(crt, qbaPassPhrase);
if (!tmpKey.isNull())
qCritical("MetaParams: Using private key found in certificate file.");
}
if (tmpKey.isNull()) {
qCritical("MetaParams: No private key found in certificate or key file.");
return false;
}
QList< QSslCertificate > ql = QSslCertificate::fromData(crt);
ql << QSslCertificate::fromData(key);
for (int i = 0; i < ql.size(); ++i) {
const QSslCertificate &c = ql.at(i);
if (Server::isKeyForCert(tmpKey, c)) {
tmpCert = c;
ql.removeAt(i);
break;
}
}
if (tmpCert.isNull()) {
qCritical("MetaParams: Failed to find certificate matching private key.");
return false;
}
if (ql.size() > 0) {
tmpIntermediates = ql;
qCritical("MetaParams: Adding %d intermediate certificates from certificate file.", ql.size());
}
}
QByteArray dhparams;
#if defined(USE_QSSLDIFFIEHELLMANPARAMETERS)
if (!qsSSLDHParams.isEmpty()) {
if (qsSSLDHParams.startsWith(QLatin1String("@"))) {
QString group = qsSSLDHParams.mid(1).trimmed();
QByteArray pem = FFDHE::PEMForNamedGroup(group);
if (pem.isEmpty()) {
QStringList names = FFDHE::NamedGroups();
QStringList atNames;
foreach (QString name, names) { atNames << QLatin1String("@") + name; }
QString supported = atNames.join(QLatin1String(", "));
qFatal("MetaParms: Diffie-Hellman parameters with name '%s' is not available. (Supported: %s)",
qPrintable(qsSSLDHParams), qPrintable(supported));
}
dhparams = pem;
} else {
QFile pem(qsSSLDHParams);
if (pem.open(QIODevice::ReadOnly)) {
dhparams = pem.readAll();
pem.close();
} else {
qFatal("MetaParams: Failed to read %s", qPrintable(qsSSLDHParams));
}
}
}
if (!dhparams.isEmpty()) {
QSslDiffieHellmanParameters qdhp = QSslDiffieHellmanParameters::fromEncoded(dhparams);
if (qdhp.isValid()) {
tmpDHParams = dhparams;
} else {
qFatal("MetaParams: Unable to use specified Diffie-Hellman parameters: %s", qPrintable(qdhp.errorString()));
return false;
}
}
#else
QString qsSSLDHParamsIniValue = qsSettings->value(QLatin1String("sslDHParams")).toString();
if (!qsSSLDHParamsIniValue.isEmpty()) {
qFatal("MetaParams: This version of Murmur does not support Diffie-Hellman parameters (sslDHParams). Murmur "
"will not start unless you remove the option from your murmur.ini file.");
return false;
}
#endif
{
QList< QSslCipher > ciphers = MumbleSSL::ciphersFromOpenSSLCipherString(tmpCiphersStr);
if (ciphers.isEmpty()) {
qCritical("MetaParams: Invalid sslCiphers option. Either the cipher string is invalid or none of the "
"ciphers are available: \"%s\"",
qPrintable(qsCiphers));
return false;
}
#if !defined(USE_QSSLDIFFIEHELLMANPARAMETERS)
// If the version of Qt we're building against doesn't support
// QSslDiffieHellmanParameters, then we must filter out Diffie-
// Hellman cipher suites in order to guarantee that we do not
// use Qt's default Diffie-Hellman parameters.
{
QList< QSslCipher > filtered;
foreach (QSslCipher c, ciphers) {
if (c.keyExchangeMethod() == QLatin1String("DH")) {
continue;
}
filtered << c;
}
if (ciphers.size() != filtered.size()) {
qWarning("MetaParams: Warning: all cipher suites in sslCiphers using Diffie-Hellman key exchange "
"have been removed. Qt %s does not support custom Diffie-Hellman parameters.",
qVersion());
}
tmpCiphers = filtered;
}
#else
tmpCiphers = ciphers;
#endif
QStringList pref;
foreach (QSslCipher c, tmpCiphers) { pref << c.name(); }
qWarning("MetaParams: TLS cipher preference is \"%s\"", qPrintable(pref.join(QLatin1String(":"))));
}
qscCert = tmpCert;
qlCA = tmpCA;
qlIntermediates = tmpIntermediates;
qskKey = tmpKey;
qbaDHParams = tmpDHParams;
qsCiphers = tmpCiphersStr;
qlCiphers = tmpCiphers;
qmConfig.insert(QLatin1String("certificate"), QString::fromUtf8(qscCert.toPem()));
qmConfig.insert(QLatin1String("key"), QString::fromUtf8(qskKey.toPem()));
qmConfig.insert(QLatin1String("sslCiphers"), qsCiphers);
qmConfig.insert(QLatin1String("sslDHParams"), QString::fromLatin1(qbaDHParams.constData()));
return true;
}
Meta::Meta() {
#ifdef Q_OS_WIN
QOS_VERSION qvVer;
qvVer.MajorVersion = 1;
qvVer.MinorVersion = 0;
hQoS = nullptr;
HMODULE hLib = LoadLibrary(L"qWave.dll");
if (!hLib) {
qWarning("Meta: Failed to load qWave.dll, no QoS available");
} else {
FreeLibrary(hLib);
if (!QOSCreateHandle(&qvVer, &hQoS))
qWarning("Meta: Failed to create QOS2 handle");
else
Connection::setQoS(hQoS);
}
#endif
}
Meta::~Meta() {
#ifdef Q_OS_WIN
if (hQoS) {
QOSCloseHandle(hQoS);
Connection::setQoS(nullptr);
}
#endif
}
bool Meta::reloadSSLSettings() {
// Reload SSL settings.
if (!Meta::mp.loadSSLSettings()) {
return false;
}
// Re-initialize certificates for all
// virtual servers using the Meta server's
// certificate and private key.
foreach (Server *s, qhServers) {
if (s->bUsingMetaCert) {
s->log("Reloading certificates...");
s->initializeCert();
} else {
s->log("Not reloading certificates; server does not use Meta certificate");
}
}
return true;
}
void Meta::getOSInfo() {
qsOS = OSInfo::getOS();
qsOSVersion = OSInfo::getOSDisplayableVersion();
}
void Meta::bootAll() {
QList< int > ql = ServerDB::getBootServers();
foreach (int snum, ql)
boot(snum);
}
bool Meta::boot(int srvnum) {
if (qhServers.contains(srvnum))
return false;
if (!ServerDB::serverExists(srvnum))
return false;
Server *s = new Server(srvnum, this);
if (!s->bValid) {
delete s;
return false;
}
qhServers.insert(srvnum, s);
emit started(s);
#ifdef Q_OS_UNIX
unsigned int sockets = 19; // Base
foreach (s, qhServers) {
sockets += 11; // Listen sockets, signal pipes etc.
sockets += s->iMaxUsers; // One per user
}
struct rlimit r;
if (getrlimit(RLIMIT_NOFILE, &r) == 0) {
if (r.rlim_cur < sockets) {
r.rlim_cur = sockets;
if (r.rlim_max < sockets)
r.rlim_max = sockets;
if (setrlimit(RLIMIT_NOFILE, &r) != 0) {
getrlimit(RLIMIT_NOFILE, &r);
if (r.rlim_cur < r.rlim_max) {
r.rlim_cur = r.rlim_max;
setrlimit(RLIMIT_NOFILE, &r);
getrlimit(RLIMIT_NOFILE, &r);
}
}
}
if (r.rlim_cur < sockets)
qCritical(
"Current booted servers require minimum %d file descriptors when all slots are full, but only %lu file "
"descriptors are allowed for this process. Your server will crash and burn; read the FAQ for details.",
sockets, static_cast< unsigned long >(r.rlim_cur));
}
#endif
return true;
}
void Meta::kill(int srvnum) {
Server *s = qhServers.take(srvnum);
if (!s)
return;
emit stopped(s);
delete s;
}
void Meta::killAll() {
foreach (Server *s, qhServers) {
emit stopped(s);
delete s;
}
qhServers.clear();
}
void Meta::successfulConnectionFrom(const QHostAddress &addr) {
if (!mp.bBanSuccessful) {
QList< Timer > &ql = qhAttempts[addr];
// Seems like this is the most efficient way to clear the list, given:
// 1. ql.clear() allocates a new array
// 2. ql has less than iBanAttempts members
// 3. seems like ql.removeFirst() might actually copy elements to shift to the front
while (!ql.empty()) {
ql.removeLast();
}
}
}
bool Meta::banCheck(const QHostAddress &addr) {
if ((mp.iBanTries <= 0) || (mp.iBanTimeframe <= 0))
return false;
if (qhBans.contains(addr)) {
Timer t = qhBans.value(addr);
if (t.elapsed() < (1000000ULL * mp.iBanTime))
return true;
qhBans.remove(addr);
}
QList< Timer > &ql = qhAttempts[addr];
ql.append(Timer());
while (!ql.isEmpty() && (ql.at(0).elapsed() > (1000000ULL * mp.iBanTimeframe)))
ql.removeFirst();
if (ql.count() > mp.iBanTries) {
qhBans.insert(addr, Timer());
return true;
}
return false;
}