mumble/src/murmur/Meta.cpp
WGH 4db7366d12 FIX(server): Always bind to both IPv6 and IPv4 by default
The check for configured network interfaces at startup has a problem
that if IPv4 is not configured for some reason, Mumble will bind to
IPv6-only socket, and will not be available over IPv4 until manually
restarted.

There's no harm binding to the "any" IPv6 address with IPV6_V6ONLY set
to 0 even if there're no assigned addresses of either address family.
The bind will succeed either way, and will accept connections on any
new address configured in the system regardless of the address family.

QHostAddress::Any tries to setup an AF_INET6 socket with IPV6_V6ONLY
set to 0, and falls back to AF_INET in case it's unavailable.
However, it will only happen if IPv6 is completely disabled on the
system, which is a rare configuration, but nevertheless it will fall
back gracefully.

Fixes #5208
2021-07-27 20:15:59 +03:00

777 lines
26 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>
#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()) {
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);
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 = Version::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;
}