0
0
mirror of https://github.com/mumble-voip/mumble.git synced 2024-09-20 19:47:59 +00:00
mumble-voip_mumble/src/murmur/Meta.cpp

808 lines
28 KiB
C++

// Copyright 2007-2023 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>
#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 = "mumble-server.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 = 0;
iChannelNestingLimit = 10;
iChannelCountLimit = 1000;
qrUserName = QRegularExpression(QLatin1String("[ -=\\w\\[\\]\\{\\}\\(\\)\\@\\|\\.]+"));
qrChannelName = QRegularExpression(QLatin1String("[ -=\\w\\#\\[\\]\\{\\}\\(\\)\\@\\|]+"));
iMessageLimit = 1;
iMessageBurst = 5;
iPluginMessageLimit = 4;
iPluginMessageBurst = 15;
broadcastListenerVolumeAdjustments = false;
qsCiphers = MumbleSSL::defaultOpenSSLCipherString();
bLogGroupChanges = false;
bLogACLChanges = false;
allowRecording = true;
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 ValueType, class ReturnType >
ReturnType MetaParams::typeCheckedFromSettings(const QString &name, const ValueType &defaultValue,
QSettings *settings) {
// Use qsSettings unless a specific QSettings instance
// is requested.
if (!settings) {
settings = qsSettings;
}
QVariant cfgVariable = settings->value(name, defaultValue);
// 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).
#if QT_VERSION >= 0x060000
if (!cfgVariable.convert(QMetaType(QVariant(defaultValue).metaType()))) {
#else
if (!cfgVariable.convert(static_cast< int >(QVariant(defaultValue).type()))) {
#endif
qCritical() << "Configuration variable" << name << "is of invalid format. Set to default value of"
<< defaultValue << ".";
return static_cast< ReturnType >(defaultValue);
}
return cfgVariable.value< ReturnType >();
}
void MetaParams::read(QString fname) {
qmConfig.clear();
if (fname.isEmpty()) {
QStringList datapaths;
#if defined(Q_OS_WIN)
datapaths << QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
QDir appdir = QDir(QDir::fromNativeSeparators(EnvUtils::getenv(QLatin1String("APPDATA"))));
datapaths << appdir.absolutePath() + QLatin1String("/Mumble");
#else
datapaths << QDir::homePath() + QLatin1String("/.murmurd");
datapaths << QDir::homePath() + QLatin1String("/.mumble-server");
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();
for (const QString &p : datapaths) {
if (!p.isEmpty()) {
// Prefer "mumble-server.ini" but for legacy reasons also keep looking for "murmur.ini"
for (const QString &currentFileName :
{ QStringLiteral("mumble-server.ini"), QStringLiteral("murmur.ini") }) {
QFileInfo fi(p, currentFileName);
if (fi.exists() && fi.isReadable()) {
qdBasePath = QDir(p);
qsAbsSettingsFilePath = fi.absoluteFilePath();
break;
}
}
}
}
if (qsAbsSettingsFilePath.isEmpty()) {
qdBasePath = QDir(datapaths.at(0));
qsAbsSettingsFilePath = qdBasePath.absolutePath() + QLatin1String("/mumble-server.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);
#if QT_VERSION < 0x060000
qsSettings->setIniCodec("UTF-8");
#endif
qsSettings->sync();
switch (qsSettings->status()) {
case QSettings::NoError:
break;
case QSettings::AccessError:
qFatal("Access error while trying to access %s", qPrintable(qsSettings->fileName()));
break;
case QSettings::FormatError:
qFatal("Your INI file at %s is invalid - check for syntax errors!", qPrintable(qsSettings->fileName()));
break;
}
if (QFile::exists(qsAbsSettingsFilePath)) {
qWarning("Initializing settings from %s (basepath %s)", qPrintable(qsSettings->fileName()),
qPrintable(qdBasePath.absolutePath()));
} else {
qWarning("No ini file at %s (basepath %s). Initializing with default settings.",
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(QRegularExpression(QLatin1String("\\s+")), Qt::SkipEmptyParts)) {
#else
// Qt 5.14 introduced the Qt::SplitBehavior flags deprecating the QString fields
foreach (const QString &host,
qsHost.split(QRegularExpression(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()) {
qlBind << QHostAddress(QHostAddress::Any);
}
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);
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);
m_suggestVersion = Version::fromConfig(qsSettings->value("suggestVersion"));
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);
allowRecording = typeCheckedFromSettings("allowRecording", allowRecording);
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 = QRegularExpression(typeCheckedFromSettings("username", qrUserName.pattern()));
qrChannelName = QRegularExpression(typeCheckedFromSettings("channelname", qrChannelName.pattern()));
iMessageLimit = typeCheckedFromSettings< unsigned int >("messagelimit", 1);
iMessageBurst = typeCheckedFromSettings< unsigned int >("messageburst", 5);
iPluginMessageLimit = typeCheckedFromSettings< unsigned int >("pluginmessagelimit", 4);
iPluginMessageBurst = typeCheckedFromSettings< unsigned int >("pluginmessageburst", 15);
broadcastListenerVolumeAdjustments = typeCheckedFromSettings("broadcastlistenervolumeadjustments", false);
bool bObfuscate = typeCheckedFromSettings("obfuscate", false);
if (bObfuscate) {
qWarning("IP address obfuscation enabled.");
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
iObfuscate = static_cast< int >(QRandomGenerator::global()->generate());
#else
// Qt 5.10 introduces the QRandomGenerator class and in Qt 5.15 qrand got deprecated in its favor
iObfuscate = static_cast< int >(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"), Version::toConfigString(m_suggestVersion));
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);
#if QT_VERSION < 0x060000
updatedSettings.setIniCodec("UTF-8");
#endif
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 %lld intermediate certificates from certificate file.",
static_cast< qsizetype >(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 mumble-server.ini (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 += static_cast< unsigned int >(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 * static_cast< unsigned long long >(mp.iBanTime)))
return true;
qhBans.remove(addr);
}
QList< Timer > &ql = qhAttempts[addr];
ql.append(Timer());
while (!ql.isEmpty() && (ql.at(0).elapsed() > (1000000ULL * static_cast< unsigned long long >(mp.iBanTimeframe))))
ql.removeFirst();
if (ql.count() > mp.iBanTries) {
qhBans.insert(addr, Timer());
return true;
}
return false;
}