mirror of
https://github.com/znc/znc.git
synced 2026-03-28 17:42:41 +01:00
This is a way for admins to mitigate some issues caused by caps if such issues ever arise. E.g. add this to global level in znc.conf: DisableClientCap = sasl DisableServerCap = chghost DisableServerCap = message-tags Then these caps will be NAKed to client / not requested from server. Note that this mechanism doesn't fully prevent a cap from being activated, e.g. one could use *send_raw module to request it from server even when disabled.
2198 lines
66 KiB
C++
2198 lines
66 KiB
C++
/*
|
|
* Copyright (C) 2004-2025 ZNC, see the NOTICE file for details.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <znc/znc.h>
|
|
#include <znc/FileUtils.h>
|
|
#include <znc/IRCSock.h>
|
|
#include <znc/Server.h>
|
|
#include <znc/User.h>
|
|
#include <znc/IRCNetwork.h>
|
|
#include <znc/Config.h>
|
|
#include <time.h>
|
|
#include <tuple>
|
|
#include <algorithm>
|
|
|
|
using std::endl;
|
|
using std::cout;
|
|
using std::map;
|
|
using std::set;
|
|
using std::vector;
|
|
using std::list;
|
|
using std::tuple;
|
|
using std::make_tuple;
|
|
|
|
CZNC::CZNC()
|
|
: m_TimeStarted(time(nullptr)),
|
|
m_eConfigState(ECONFIG_NOTHING),
|
|
m_vpListeners(),
|
|
m_msUsers(),
|
|
m_msDelUsers(),
|
|
m_Manager(),
|
|
m_sCurPath(""),
|
|
m_sZNCPath(""),
|
|
m_sConfigFile(""),
|
|
m_sSkinName(""),
|
|
m_sStatusPrefix(""),
|
|
m_sPidFile(""),
|
|
m_sSSLCertFile(""),
|
|
m_sSSLKeyFile(""),
|
|
m_sSSLDHParamFile(""),
|
|
m_sSSLCiphers(""),
|
|
m_sSSLProtocols(""),
|
|
m_vsBindHosts(),
|
|
m_vsTrustedProxies(),
|
|
m_vsMotd(),
|
|
m_ssClientCapBlacklist(),
|
|
m_ssServerCapBlacklist(),
|
|
m_pLockFile(nullptr),
|
|
m_uiConnectDelay(5),
|
|
m_uiAnonIPLimit(10),
|
|
m_uiMaxBufferSize(500),
|
|
m_uDisabledSSLProtocols(Csock::EDP_SSL | Csock::EDP_TLSv1 |
|
|
Csock::EDP_TLSv1_1),
|
|
m_pModules(new CModules),
|
|
m_uBytesRead(0),
|
|
m_uBytesWritten(0),
|
|
m_lpConnectQueue(),
|
|
m_pConnectQueueTimer(nullptr),
|
|
m_uiConnectPaused(0),
|
|
m_uiForceEncoding(0),
|
|
m_sConnectThrottle(),
|
|
m_bProtectWebSessions(true),
|
|
m_bHideVersion(false),
|
|
m_bAuthOnlyViaModule(false),
|
|
m_Translation("znc"),
|
|
m_uiConfigWriteDelay(0),
|
|
m_pConfigTimer(nullptr) {
|
|
if (!InitCsocket()) {
|
|
CUtils::PrintError("Could not initialize Csocket!");
|
|
exit(-1);
|
|
}
|
|
m_sConnectThrottle.SetTTL(30000);
|
|
}
|
|
|
|
CZNC::~CZNC() {
|
|
m_pModules->UnloadAll();
|
|
|
|
for (const auto& it : m_msUsers) {
|
|
it.second->GetModules().UnloadAll();
|
|
|
|
const vector<CIRCNetwork*>& networks = it.second->GetNetworks();
|
|
for (CIRCNetwork* pNetwork : networks) {
|
|
pNetwork->GetModules().UnloadAll();
|
|
}
|
|
}
|
|
|
|
for (CListener* pListener : m_vpListeners) {
|
|
delete pListener;
|
|
}
|
|
|
|
for (const auto& it : m_msUsers) {
|
|
it.second->SetBeingDeleted(true);
|
|
}
|
|
|
|
m_pConnectQueueTimer = nullptr;
|
|
// This deletes m_pConnectQueueTimer
|
|
m_Manager.Cleanup();
|
|
DeleteUsers();
|
|
|
|
delete m_pModules;
|
|
delete m_pLockFile;
|
|
|
|
ShutdownCsocket();
|
|
DeletePidFile();
|
|
}
|
|
|
|
CString CZNC::GetVersion() {
|
|
return CString(VERSION_STR) + CString(ZNC_VERSION_EXTRA);
|
|
}
|
|
|
|
CString CZNC::GetTag(bool bIncludeVersion, bool bHTML) {
|
|
if (!Get().m_bHideVersion) {
|
|
bIncludeVersion = true;
|
|
}
|
|
CString sAddress = bHTML ? "<a href=\"https://znc.in\">https://znc.in</a>"
|
|
: "https://znc.in";
|
|
|
|
if (!bIncludeVersion) {
|
|
return "ZNC - " + sAddress;
|
|
}
|
|
|
|
CString sVersion = GetVersion();
|
|
|
|
return "ZNC " + sVersion + " - " + sAddress;
|
|
}
|
|
|
|
CString CZNC::GetCompileOptionsString() {
|
|
return ZNC_COMPILE_OPTIONS_STRING;
|
|
}
|
|
|
|
CString CZNC::GetUptime() const {
|
|
time_t now = time(nullptr);
|
|
return CString::ToTimeStr(now - TimeStarted());
|
|
}
|
|
|
|
bool CZNC::OnBoot() {
|
|
bool bFail = false;
|
|
ALLMODULECALL(OnBoot(), &bFail);
|
|
if (bFail) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CZNC::HandleUserDeletion() {
|
|
if (m_msDelUsers.empty()) return false;
|
|
|
|
for (const auto& it : m_msDelUsers) {
|
|
CUser* pUser = it.second;
|
|
pUser->SetBeingDeleted(true);
|
|
|
|
if (GetModules().OnDeleteUser(*pUser)) {
|
|
pUser->SetBeingDeleted(false);
|
|
continue;
|
|
}
|
|
m_msUsers.erase(pUser->GetUsername());
|
|
CWebSock::FinishUserSessions(*pUser);
|
|
delete pUser;
|
|
}
|
|
|
|
m_msDelUsers.clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
class CConfigWriteTimer : public CCron {
|
|
public:
|
|
CConfigWriteTimer(int iSecs) : CCron() {
|
|
SetName("Config write timer");
|
|
Start(iSecs);
|
|
}
|
|
|
|
protected:
|
|
void RunJob() override {
|
|
CZNC::Get().SetConfigState(CZNC::ECONFIG_NEED_WRITE);
|
|
|
|
CZNC::Get().DisableConfigTimer();
|
|
}
|
|
};
|
|
|
|
void CZNC::Loop() {
|
|
while (true) {
|
|
CString sError;
|
|
|
|
ConfigState eState = GetConfigState();
|
|
switch (eState) {
|
|
case ECONFIG_NEED_REHASH:
|
|
SetConfigState(ECONFIG_NOTHING);
|
|
|
|
if (RehashConfig(sError)) {
|
|
Broadcast("Rehashing succeeded", true);
|
|
} else {
|
|
Broadcast("Rehashing failed: " + sError, true);
|
|
Broadcast("ZNC is in some possibly inconsistent state!",
|
|
true);
|
|
}
|
|
break;
|
|
case ECONFIG_DELAYED_WRITE:
|
|
SetConfigState(ECONFIG_NOTHING);
|
|
|
|
if (GetConfigWriteDelay() > 0) {
|
|
if (m_pConfigTimer == nullptr) {
|
|
m_pConfigTimer = new CConfigWriteTimer(GetConfigWriteDelay());
|
|
GetManager().AddCron(m_pConfigTimer);
|
|
}
|
|
break;
|
|
}
|
|
/* Fall through */
|
|
case ECONFIG_NEED_WRITE:
|
|
case ECONFIG_NEED_VERBOSE_WRITE:
|
|
SetConfigState(ECONFIG_NOTHING);
|
|
|
|
// stop pending configuration timer
|
|
DisableConfigTimer();
|
|
|
|
if (!WriteConfig()) {
|
|
Broadcast("Writing the config file failed", true);
|
|
} else if (eState == ECONFIG_NEED_VERBOSE_WRITE) {
|
|
Broadcast("Writing the config succeeded", true);
|
|
}
|
|
break;
|
|
case ECONFIG_NOTHING:
|
|
break;
|
|
case ECONFIG_NEED_QUIT:
|
|
return;
|
|
}
|
|
|
|
// Check for users that need to be deleted
|
|
if (HandleUserDeletion()) {
|
|
// Also remove those user(s) from the config file
|
|
WriteConfig();
|
|
}
|
|
|
|
// Csocket wants micro seconds
|
|
// 100 msec to 5 min
|
|
m_Manager.DynamicSelectLoop(100 * 1000, 5 * 60 * 1000 * 1000);
|
|
}
|
|
}
|
|
|
|
CFile* CZNC::InitPidFile() {
|
|
if (!m_sPidFile.empty()) {
|
|
CString sFile;
|
|
|
|
// absolute path or relative to the data dir?
|
|
if (m_sPidFile[0] != '/')
|
|
sFile = GetZNCPath() + "/" + m_sPidFile;
|
|
else
|
|
sFile = m_sPidFile;
|
|
|
|
return new CFile(sFile);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool CZNC::WritePidFile(int iPid) {
|
|
CFile* File = InitPidFile();
|
|
if (File == nullptr) return false;
|
|
|
|
CUtils::PrintAction("Writing pid file [" + File->GetLongName() + "]");
|
|
|
|
bool bRet = false;
|
|
if (File->Open(O_WRONLY | O_TRUNC | O_CREAT)) {
|
|
File->Write(CString(iPid) + "\n");
|
|
File->Close();
|
|
bRet = true;
|
|
}
|
|
|
|
delete File;
|
|
CUtils::PrintStatus(bRet);
|
|
return bRet;
|
|
}
|
|
|
|
bool CZNC::DeletePidFile() {
|
|
CFile* File = InitPidFile();
|
|
if (File == nullptr) return false;
|
|
|
|
CUtils::PrintAction("Deleting pid file [" + File->GetLongName() + "]");
|
|
|
|
bool bRet = File->Delete();
|
|
|
|
delete File;
|
|
CUtils::PrintStatus(bRet);
|
|
return bRet;
|
|
}
|
|
|
|
bool CZNC::WritePemFile() {
|
|
#ifndef HAVE_LIBSSL
|
|
CUtils::PrintError("ZNC was not compiled with ssl support.");
|
|
return false;
|
|
#else
|
|
CString sPemFile = GetPemLocation();
|
|
|
|
CUtils::PrintAction("Writing Pem file [" + sPemFile + "]");
|
|
#ifndef _WIN32
|
|
int fd = creat(sPemFile.c_str(), 0600);
|
|
if (fd == -1) {
|
|
CUtils::PrintStatus(false, "Unable to open");
|
|
return false;
|
|
}
|
|
FILE* f = fdopen(fd, "w");
|
|
#else
|
|
FILE* f = fopen(sPemFile.c_str(), "w");
|
|
#endif
|
|
|
|
if (!f) {
|
|
CUtils::PrintStatus(false, "Unable to open");
|
|
return false;
|
|
}
|
|
|
|
CUtils::GenerateCert(f);
|
|
fclose(f);
|
|
|
|
CUtils::PrintStatus(true);
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
void CZNC::DeleteUsers() {
|
|
for (const auto& it : m_msUsers) {
|
|
it.second->SetBeingDeleted(true);
|
|
delete it.second;
|
|
}
|
|
|
|
m_msUsers.clear();
|
|
DisableConnectQueue();
|
|
}
|
|
|
|
bool CZNC::IsHostAllowed(const CString& sHostMask) const {
|
|
for (const auto& it : m_msUsers) {
|
|
if (it.second->IsHostAllowed(sHostMask)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CZNC::AllowConnectionFrom(const CString& sIP) const {
|
|
if (m_uiAnonIPLimit == 0) return true;
|
|
return (GetManager().GetAnonConnectionCount(sIP) < m_uiAnonIPLimit);
|
|
}
|
|
|
|
void CZNC::InitDirs(const CString& sArgvPath, const CString& sDataDir) {
|
|
// If the bin was not ran from the current directory, we need to add that
|
|
// dir onto our cwd
|
|
CString::size_type uPos = sArgvPath.rfind('/');
|
|
if (uPos == CString::npos)
|
|
m_sCurPath = "./";
|
|
else
|
|
m_sCurPath = CDir::ChangeDir("./", sArgvPath.Left(uPos), "");
|
|
|
|
// Try to set the user's home dir, default to binpath on failure
|
|
CFile::InitHomePath(m_sCurPath);
|
|
|
|
if (sDataDir.empty()) {
|
|
m_sZNCPath = CFile::GetHomePath() + "/.znc";
|
|
} else {
|
|
m_sZNCPath = sDataDir;
|
|
}
|
|
|
|
m_sSSLCertFile = m_sZNCPath + "/znc.pem";
|
|
}
|
|
|
|
CString CZNC::GetConfPath(bool bAllowMkDir) const {
|
|
CString sConfPath = m_sZNCPath + "/configs";
|
|
if (bAllowMkDir && !CFile::Exists(sConfPath)) {
|
|
CDir::MakeDir(sConfPath);
|
|
}
|
|
|
|
return sConfPath;
|
|
}
|
|
|
|
CString CZNC::GetUserPath() const {
|
|
CString sUserPath = m_sZNCPath + "/users";
|
|
if (!CFile::Exists(sUserPath)) {
|
|
CDir::MakeDir(sUserPath);
|
|
}
|
|
|
|
return sUserPath;
|
|
}
|
|
|
|
CString CZNC::GetModPath() const {
|
|
CString sModPath = m_sZNCPath + "/modules";
|
|
|
|
return sModPath;
|
|
}
|
|
|
|
const CString& CZNC::GetCurPath() const {
|
|
if (!CFile::Exists(m_sCurPath)) {
|
|
CDir::MakeDir(m_sCurPath);
|
|
}
|
|
return m_sCurPath;
|
|
}
|
|
|
|
const CString& CZNC::GetHomePath() const { return CFile::GetHomePath(); }
|
|
|
|
const CString& CZNC::GetZNCPath() const {
|
|
if (!CFile::Exists(m_sZNCPath)) {
|
|
CDir::MakeDir(m_sZNCPath);
|
|
}
|
|
return m_sZNCPath;
|
|
}
|
|
|
|
CString CZNC::GetPemLocation() const {
|
|
return CDir::ChangeDir("", m_sSSLCertFile);
|
|
}
|
|
|
|
CString CZNC::GetKeyLocation() const {
|
|
return CDir::ChangeDir(
|
|
"", m_sSSLKeyFile.empty() ? m_sSSLCertFile : m_sSSLKeyFile);
|
|
}
|
|
|
|
CString CZNC::GetDHParamLocation() const {
|
|
return CDir::ChangeDir(
|
|
"", m_sSSLDHParamFile.empty() ? m_sSSLCertFile : m_sSSLDHParamFile);
|
|
}
|
|
|
|
CString CZNC::ExpandConfigPath(const CString& sConfigFile, bool bAllowMkDir) {
|
|
CString sRetPath;
|
|
|
|
if (sConfigFile.empty()) {
|
|
sRetPath = GetConfPath(bAllowMkDir) + "/znc.conf";
|
|
} else {
|
|
if (sConfigFile.StartsWith("./") || sConfigFile.StartsWith("../")) {
|
|
sRetPath = GetCurPath() + "/" + sConfigFile;
|
|
} else if (!sConfigFile.StartsWith("/")) {
|
|
sRetPath = GetConfPath(bAllowMkDir) + "/" + sConfigFile;
|
|
} else {
|
|
sRetPath = sConfigFile;
|
|
}
|
|
}
|
|
|
|
return sRetPath;
|
|
}
|
|
|
|
bool CZNC::WriteConfig() {
|
|
if (GetConfigFile().empty()) {
|
|
DEBUG("Config file name is empty?!");
|
|
return false;
|
|
}
|
|
|
|
// We first write to a temporary file and then move it to the right place
|
|
CFile* pFile = new CFile(GetConfigFile() + "~");
|
|
|
|
if (!pFile->Open(O_WRONLY | O_CREAT | O_TRUNC, 0600)) {
|
|
DEBUG("Could not write config to " + GetConfigFile() + "~: " +
|
|
CString(strerror(errno)));
|
|
delete pFile;
|
|
return false;
|
|
}
|
|
|
|
// We have to "transfer" our lock on the config to the new file.
|
|
// The old file (= inode) is going away and thus a lock on it would be
|
|
// useless. These lock should always succeed (races, anyone?).
|
|
if (!pFile->TryExLock()) {
|
|
DEBUG("Error while locking the new config file, errno says: " +
|
|
CString(strerror(errno)));
|
|
pFile->Delete();
|
|
delete pFile;
|
|
return false;
|
|
}
|
|
|
|
pFile->Write(MakeConfigHeader() + "\n");
|
|
|
|
CConfig config;
|
|
config.AddKeyValuePair("AnonIPLimit", CString(m_uiAnonIPLimit));
|
|
config.AddKeyValuePair("MaxBufferSize", CString(m_uiMaxBufferSize));
|
|
config.AddKeyValuePair("SSLCertFile", CString(GetPemLocation()));
|
|
config.AddKeyValuePair("SSLKeyFile", CString(GetKeyLocation()));
|
|
config.AddKeyValuePair("SSLDHParamFile", CString(GetDHParamLocation()));
|
|
config.AddKeyValuePair("ProtectWebSessions",
|
|
CString(m_bProtectWebSessions));
|
|
config.AddKeyValuePair("HideVersion", CString(m_bHideVersion));
|
|
config.AddKeyValuePair("AuthOnlyViaModule", CString(m_bAuthOnlyViaModule));
|
|
config.AddKeyValuePair("Version", CString(VERSION_STR));
|
|
config.AddKeyValuePair("ConfigWriteDelay", CString(m_uiConfigWriteDelay));
|
|
|
|
unsigned int l = 0;
|
|
for (CListener* pListener : m_vpListeners) {
|
|
config.AddSubConfig("Listener", "listener" + CString(l++),
|
|
pListener->ToConfig());
|
|
}
|
|
|
|
config.AddKeyValuePair("ConnectDelay", CString(m_uiConnectDelay));
|
|
config.AddKeyValuePair("ServerThrottle",
|
|
CString(m_sConnectThrottle.GetTTL() / 1000));
|
|
|
|
if (!m_sPidFile.empty()) {
|
|
config.AddKeyValuePair("PidFile", m_sPidFile.FirstLine());
|
|
}
|
|
|
|
if (!m_sSkinName.empty()) {
|
|
config.AddKeyValuePair("Skin", m_sSkinName.FirstLine());
|
|
}
|
|
|
|
if (!m_sStatusPrefix.empty()) {
|
|
config.AddKeyValuePair("StatusPrefix", m_sStatusPrefix.FirstLine());
|
|
}
|
|
|
|
if (!m_sSSLCiphers.empty()) {
|
|
config.AddKeyValuePair("SSLCiphers", CString(m_sSSLCiphers));
|
|
}
|
|
|
|
if (!m_sSSLProtocols.empty()) {
|
|
config.AddKeyValuePair("SSLProtocols", m_sSSLProtocols);
|
|
}
|
|
|
|
for (const CString& sLine : m_vsMotd) {
|
|
config.AddKeyValuePair("Motd", sLine.FirstLine());
|
|
}
|
|
|
|
for (const CString& sProxy : m_vsTrustedProxies) {
|
|
config.AddKeyValuePair("TrustedProxy", sProxy.FirstLine());
|
|
}
|
|
|
|
CModules& Mods = GetModules();
|
|
|
|
for (const CModule* pMod : Mods) {
|
|
CString sName = pMod->GetModName();
|
|
CString sArgs = pMod->GetArgs();
|
|
|
|
if (!sArgs.empty()) {
|
|
sArgs = " " + sArgs.FirstLine();
|
|
}
|
|
|
|
config.AddKeyValuePair("LoadModule", sName.FirstLine() + sArgs);
|
|
}
|
|
|
|
for (const auto& it : m_msUsers) {
|
|
CString sErr;
|
|
|
|
if (!it.second->IsValid(sErr)) {
|
|
DEBUG("** Error writing config for user [" << it.first << "] ["
|
|
<< sErr << "]");
|
|
continue;
|
|
}
|
|
|
|
config.AddSubConfig("User", it.second->GetUsername(),
|
|
it.second->ToConfig());
|
|
}
|
|
|
|
config.Write(*pFile);
|
|
|
|
// If Sync() fails... well, let's hope nothing important breaks..
|
|
pFile->Sync();
|
|
|
|
if (pFile->HadError()) {
|
|
DEBUG("Error while writing the config, errno says: " +
|
|
CString(strerror(errno)));
|
|
pFile->Delete();
|
|
delete pFile;
|
|
return false;
|
|
}
|
|
|
|
// We wrote to a temporary name, move it to the right place
|
|
if (!pFile->Move(GetConfigFile(), true)) {
|
|
DEBUG(
|
|
"Error while replacing the config file with a new version, errno "
|
|
"says "
|
|
<< strerror(errno));
|
|
pFile->Delete();
|
|
delete pFile;
|
|
return false;
|
|
}
|
|
|
|
// Everything went fine, just need to update the saved path.
|
|
pFile->SetFileName(GetConfigFile());
|
|
|
|
// Make sure the lock is kept alive as long as we need it.
|
|
delete m_pLockFile;
|
|
m_pLockFile = pFile;
|
|
|
|
return true;
|
|
}
|
|
|
|
CString CZNC::MakeConfigHeader() {
|
|
return "// WARNING\n"
|
|
"//\n"
|
|
"// Do NOT edit this file while ZNC is running!\n"
|
|
"// Use webadmin or *controlpanel instead.\n"
|
|
"//\n"
|
|
"// Altering this file by hand will forfeit all support.\n"
|
|
"//\n"
|
|
"// But if you feel risky, you might want to read help on /znc "
|
|
"saveconfig and /znc rehash.\n"
|
|
"// Also check https://wiki.znc.in/Configuration\n";
|
|
}
|
|
|
|
bool CZNC::WriteNewConfig(const CString& sConfigFile) {
|
|
CString sAnswer, sUser, sNetwork;
|
|
VCString vsLines;
|
|
|
|
vsLines.push_back(MakeConfigHeader());
|
|
vsLines.push_back("Version = " + CString(VERSION_STR));
|
|
|
|
m_sConfigFile = ExpandConfigPath(sConfigFile);
|
|
|
|
if (CFile::Exists(m_sConfigFile)) {
|
|
CUtils::PrintStatus(
|
|
false, "WARNING: config [" + m_sConfigFile + "] already exists.");
|
|
}
|
|
|
|
CUtils::PrintMessage("");
|
|
CUtils::PrintMessage("-- Global settings --");
|
|
CUtils::PrintMessage("");
|
|
|
|
// Listen
|
|
#ifdef HAVE_IPV6
|
|
bool b6 = true;
|
|
#else
|
|
bool b6 = false;
|
|
#endif
|
|
CString sListenHost;
|
|
CString sURIPrefix;
|
|
bool bListenSSL = false;
|
|
unsigned int uListenPort = 0;
|
|
bool bSuccess;
|
|
|
|
// Unix sockets are not exposed in --makeconf by default, but it's possible
|
|
// to trigger this using env var. This is mostly useful for the integration
|
|
// test.
|
|
char* szListenUnixSocket = getenv("ZNC_LISTEN_UNIX_SOCKET");
|
|
if (!szListenUnixSocket) {
|
|
do {
|
|
bSuccess = true;
|
|
while (true) {
|
|
if (!CUtils::GetNumInput("Listen on port", uListenPort, 1025,
|
|
65534)) {
|
|
continue;
|
|
}
|
|
if (uListenPort == 6667 || uListenPort == 6697) {
|
|
CUtils::PrintStatus(
|
|
false,
|
|
"WARNING: Some web browsers reject ports "
|
|
"6667 and 6697. If you intend to");
|
|
CUtils::PrintStatus(
|
|
false,
|
|
"use ZNC's web interface, you might want "
|
|
"to use another port.");
|
|
if (!CUtils::GetBoolInput("Proceed anyway?", true)) {
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
#ifdef HAVE_LIBSSL
|
|
bListenSSL = CUtils::GetBoolInput("Listen using SSL", bListenSSL);
|
|
#endif
|
|
|
|
#ifdef HAVE_IPV6
|
|
b6 = CUtils::GetBoolInput("Listen using both IPv4 and IPv6", b6);
|
|
#endif
|
|
|
|
// Don't ask for listen host, it may be configured later if needed.
|
|
|
|
CUtils::PrintAction("Verifying the listener");
|
|
CListener* pListener = new CTCPListener(
|
|
(unsigned short int)uListenPort, sListenHost, sURIPrefix,
|
|
bListenSSL, b6 ? ADDR_ALL : ADDR_IPV4ONLY,
|
|
CListener::ACCEPT_ALL);
|
|
if (!pListener->Listen()) {
|
|
CUtils::PrintStatus(false, FormatBindError());
|
|
bSuccess = false;
|
|
} else
|
|
CUtils::PrintStatus(true);
|
|
delete pListener;
|
|
} while (!bSuccess);
|
|
}
|
|
|
|
#ifdef HAVE_LIBSSL
|
|
CString sPemFile = GetPemLocation();
|
|
if (!CFile::Exists(sPemFile)) {
|
|
CUtils::PrintMessage("Unable to locate pem file: [" + sPemFile +
|
|
"], creating it");
|
|
WritePemFile();
|
|
}
|
|
#endif
|
|
|
|
vsLines.push_back("<Listener l>");
|
|
if (szListenUnixSocket) {
|
|
vsLines.push_back("\tPath = " + CString(szListenUnixSocket));
|
|
} else {
|
|
vsLines.push_back("\tPort = " + CString(uListenPort));
|
|
vsLines.push_back("\tIPv4 = true");
|
|
vsLines.push_back("\tIPv6 = " + CString(b6));
|
|
}
|
|
vsLines.push_back("\tSSL = " + CString(bListenSSL));
|
|
if (!sListenHost.empty()) {
|
|
vsLines.push_back("\tHost = " + sListenHost);
|
|
}
|
|
vsLines.push_back("</Listener>");
|
|
// !Listen
|
|
|
|
set<CModInfo> ssGlobalMods;
|
|
GetModules().GetDefaultMods(ssGlobalMods, CModInfo::GlobalModule);
|
|
vector<CString> vsGlobalModNames;
|
|
for (const CModInfo& Info : ssGlobalMods) {
|
|
vsGlobalModNames.push_back(Info.GetName());
|
|
vsLines.push_back("LoadModule = " + Info.GetName());
|
|
}
|
|
CUtils::PrintMessage(
|
|
"Enabled global modules [" +
|
|
CString(", ").Join(vsGlobalModNames.begin(), vsGlobalModNames.end()) +
|
|
"]");
|
|
|
|
// User
|
|
CUtils::PrintMessage("");
|
|
CUtils::PrintMessage("-- Admin user settings --");
|
|
CUtils::PrintMessage("");
|
|
|
|
vsLines.push_back("");
|
|
CString sNick;
|
|
do {
|
|
CUtils::GetInput("Username", sUser, "", "alphanumeric");
|
|
} while (!CUser::IsValidUsername(sUser));
|
|
|
|
vsLines.push_back("<User " + sUser + ">");
|
|
sAnswer = CUtils::AskSaltedHashPassForConfig();
|
|
vsLines.push_back(sAnswer);
|
|
|
|
vsLines.push_back("\tAdmin = true");
|
|
|
|
CUtils::GetInput("Nick", sNick, CUser::MakeCleanUsername(sUser));
|
|
vsLines.push_back("\tNick = " + sNick);
|
|
CUtils::GetInput("Alternate nick", sAnswer, sNick + "_");
|
|
if (!sAnswer.empty()) {
|
|
vsLines.push_back("\tAltNick = " + sAnswer);
|
|
}
|
|
CUtils::GetInput("Ident", sAnswer, sUser);
|
|
vsLines.push_back("\tIdent = " + sAnswer);
|
|
CUtils::GetInput("Real name", sAnswer, "", "optional");
|
|
if (!sAnswer.empty()) {
|
|
vsLines.push_back("\tRealName = " + sAnswer);
|
|
}
|
|
CUtils::GetInput("Bind host", sAnswer, "", "optional");
|
|
if (!sAnswer.empty()) {
|
|
vsLines.push_back("\tBindHost = " + sAnswer);
|
|
}
|
|
|
|
set<CModInfo> ssUserMods;
|
|
GetModules().GetDefaultMods(ssUserMods, CModInfo::UserModule);
|
|
vector<CString> vsUserModNames;
|
|
for (const CModInfo& Info : ssUserMods) {
|
|
vsUserModNames.push_back(Info.GetName());
|
|
vsLines.push_back("\tLoadModule = " + Info.GetName());
|
|
}
|
|
CUtils::PrintMessage(
|
|
"Enabled user modules [" +
|
|
CString(", ").Join(vsUserModNames.begin(), vsUserModNames.end()) + "]");
|
|
|
|
CUtils::PrintMessage("");
|
|
if (CUtils::GetBoolInput("Set up a network?", true)) {
|
|
vsLines.push_back("");
|
|
|
|
CUtils::PrintMessage("");
|
|
CUtils::PrintMessage("-- Network settings --");
|
|
CUtils::PrintMessage("");
|
|
|
|
do {
|
|
CUtils::GetInput("Name", sNetwork, "libera");
|
|
} while (!CIRCNetwork::IsValidNetwork(sNetwork));
|
|
|
|
vsLines.push_back("\t<Network " + sNetwork + ">");
|
|
|
|
set<CModInfo> ssNetworkMods;
|
|
GetModules().GetDefaultMods(ssNetworkMods, CModInfo::NetworkModule);
|
|
vector<CString> vsNetworkModNames;
|
|
for (const CModInfo& Info : ssNetworkMods) {
|
|
vsNetworkModNames.push_back(Info.GetName());
|
|
vsLines.push_back("\t\tLoadModule = " + Info.GetName());
|
|
}
|
|
|
|
CString sHost, sPass, sHint;
|
|
bool bSSL = false;
|
|
unsigned int uServerPort = 0;
|
|
|
|
if (sNetwork.Equals("libera")) {
|
|
sHost = "irc.libera.chat";
|
|
#ifdef HAVE_LIBSSL
|
|
bSSL = true;
|
|
#endif
|
|
} else {
|
|
sHint = "host only";
|
|
}
|
|
|
|
while (!CUtils::GetInput("Server host", sHost, sHost, sHint) ||
|
|
!CServer::IsValidHostName(sHost))
|
|
;
|
|
#ifdef HAVE_LIBSSL
|
|
bSSL = CUtils::GetBoolInput("Server uses SSL?", bSSL);
|
|
#endif
|
|
while (!CUtils::GetNumInput("Server port", uServerPort, 1, 65535,
|
|
bSSL ? 6697 : 6667));
|
|
CUtils::GetInput("Server password (probably empty)", sPass);
|
|
|
|
if (sHost.StartsWith("unix:")) {
|
|
vsLines.push_back("\t\tServer = " + sHost + " " + sPass);
|
|
} else {
|
|
vsLines.push_back("\t\tServer = " + sHost +
|
|
((bSSL) ? " +" : " ") + CString(uServerPort) +
|
|
" " + sPass);
|
|
}
|
|
|
|
CString sChans;
|
|
if (CUtils::GetInput("Initial channels", sChans)) {
|
|
vsLines.push_back("");
|
|
VCString vsChans;
|
|
sChans.Replace(",", " ");
|
|
sChans.Replace(";", " ");
|
|
sChans.Split(" ", vsChans, false, "", "", true, true);
|
|
for (const CString& sChan : vsChans) {
|
|
vsLines.push_back("\t\t<Chan " + sChan + ">");
|
|
vsLines.push_back("\t\t</Chan>");
|
|
}
|
|
}
|
|
|
|
CUtils::PrintMessage("Enabled network modules [" +
|
|
CString(", ").Join(vsNetworkModNames.begin(),
|
|
vsNetworkModNames.end()) +
|
|
"]");
|
|
|
|
vsLines.push_back("\t</Network>");
|
|
}
|
|
|
|
vsLines.push_back("</User>");
|
|
|
|
CUtils::PrintMessage("");
|
|
// !User
|
|
|
|
CFile File;
|
|
bool bFileOK, bFileOpen = false;
|
|
do {
|
|
CUtils::PrintAction("Writing config [" + m_sConfigFile + "]");
|
|
|
|
bFileOK = true;
|
|
if (CFile::Exists(m_sConfigFile)) {
|
|
if (!File.TryExLock(m_sConfigFile)) {
|
|
CUtils::PrintStatus(false,
|
|
"ZNC is currently running on this config.");
|
|
bFileOK = false;
|
|
} else {
|
|
File.Close();
|
|
CUtils::PrintStatus(false, "This config already exists.");
|
|
if (CUtils::GetBoolInput(
|
|
"Are you sure you want to overwrite it?", false))
|
|
CUtils::PrintAction("Overwriting config [" + m_sConfigFile +
|
|
"]");
|
|
else
|
|
bFileOK = false;
|
|
}
|
|
}
|
|
|
|
if (bFileOK) {
|
|
File.SetFileName(m_sConfigFile);
|
|
if (File.Open(O_WRONLY | O_CREAT | O_TRUNC, 0600)) {
|
|
bFileOpen = true;
|
|
} else {
|
|
CUtils::PrintStatus(false, "Unable to open file");
|
|
bFileOK = false;
|
|
}
|
|
}
|
|
if (!bFileOK) {
|
|
while (!CUtils::GetInput("Please specify an alternate location",
|
|
m_sConfigFile, "",
|
|
"or \"stdout\" for displaying the config"))
|
|
;
|
|
if (m_sConfigFile.Equals("stdout"))
|
|
bFileOK = true;
|
|
else
|
|
m_sConfigFile = ExpandConfigPath(m_sConfigFile);
|
|
}
|
|
} while (!bFileOK);
|
|
|
|
if (!bFileOpen) {
|
|
CUtils::PrintMessage("");
|
|
CUtils::PrintMessage("Printing the new config to stdout:");
|
|
CUtils::PrintMessage("");
|
|
cout << endl << "------------------------------------------------------"
|
|
"----------------------" << endl << endl;
|
|
}
|
|
|
|
for (const CString& sLine : vsLines) {
|
|
if (bFileOpen) {
|
|
File.Write(sLine + "\n");
|
|
} else {
|
|
cout << sLine << endl;
|
|
}
|
|
}
|
|
|
|
if (bFileOpen) {
|
|
File.Close();
|
|
if (File.HadError())
|
|
CUtils::PrintStatus(false,
|
|
"There was an error while writing the config");
|
|
else
|
|
CUtils::PrintStatus(true);
|
|
} else {
|
|
cout << endl << "------------------------------------------------------"
|
|
"----------------------" << endl << endl;
|
|
}
|
|
|
|
if (File.HadError()) {
|
|
bFileOpen = false;
|
|
CUtils::PrintMessage("Printing the new config to stdout instead:");
|
|
cout << endl << "------------------------------------------------------"
|
|
"----------------------" << endl << endl;
|
|
for (const CString& sLine : vsLines) {
|
|
cout << sLine << endl;
|
|
}
|
|
cout << endl << "------------------------------------------------------"
|
|
"----------------------" << endl << endl;
|
|
}
|
|
|
|
const CString sProtocol(bListenSSL ? "https" : "http");
|
|
const CString sSSL(bListenSSL ? "+" : "");
|
|
CUtils::PrintMessage("");
|
|
CUtils::PrintMessage(
|
|
"To connect to this ZNC you need to connect to it as your IRC server",
|
|
true);
|
|
CUtils::PrintMessage(
|
|
"using the port that you supplied. You have to supply your login info",
|
|
true);
|
|
CUtils::PrintMessage(
|
|
"as the IRC server password like this: user/network:pass.", true);
|
|
CUtils::PrintMessage("");
|
|
CUtils::PrintMessage("Try something like this in your IRC client...", true);
|
|
CUtils::PrintMessage("/server <znc_server_ip> " + sSSL +
|
|
CString(uListenPort) + " " + sUser + ":<pass>",
|
|
true);
|
|
CUtils::PrintMessage("");
|
|
CUtils::PrintMessage(
|
|
"To manage settings, users and networks, point your web browser to",
|
|
true);
|
|
CUtils::PrintMessage(
|
|
sProtocol + "://<znc_server_ip>:" + CString(uListenPort) + "/", true);
|
|
CUtils::PrintMessage("");
|
|
|
|
File.UnLock();
|
|
|
|
bool bWantLaunch = bFileOpen;
|
|
if (bWantLaunch) {
|
|
// "export ZNC_NO_LAUNCH_AFTER_MAKECONF=1" would cause znc --makeconf to
|
|
// not offer immediate launch.
|
|
// Useful for distros which want to create config when znc package is
|
|
// installed.
|
|
// See https://github.com/znc/znc/pull/257
|
|
char* szNoLaunch = getenv("ZNC_NO_LAUNCH_AFTER_MAKECONF");
|
|
if (szNoLaunch && *szNoLaunch == '1') {
|
|
bWantLaunch = false;
|
|
}
|
|
}
|
|
if (bWantLaunch) {
|
|
bWantLaunch = CUtils::GetBoolInput("Launch ZNC now?", true);
|
|
}
|
|
return bWantLaunch;
|
|
}
|
|
|
|
void CZNC::BackupConfigOnce(const CString& sSuffix) {
|
|
static bool didBackup = false;
|
|
if (didBackup) return;
|
|
didBackup = true;
|
|
|
|
CUtils::PrintAction("Creating a config backup");
|
|
|
|
CString sBackup = CDir::ChangeDir(m_sConfigFile, "../znc.conf." + sSuffix);
|
|
if (CFile::Copy(m_sConfigFile, sBackup))
|
|
CUtils::PrintStatus(true, sBackup);
|
|
else
|
|
CUtils::PrintStatus(false, strerror(errno));
|
|
}
|
|
|
|
bool CZNC::ParseConfig(const CString& sConfig, CString& sError) {
|
|
m_sConfigFile = ExpandConfigPath(sConfig, false);
|
|
|
|
CConfig config;
|
|
if (!ReadConfig(config, sError)) return false;
|
|
|
|
if (!LoadGlobal(config, sError)) return false;
|
|
|
|
if (!LoadUsers(config, sError)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CZNC::ReadConfig(CConfig& config, CString& sError) {
|
|
sError.clear();
|
|
|
|
CUtils::PrintAction("Opening config [" + m_sConfigFile + "]");
|
|
|
|
if (!CFile::Exists(m_sConfigFile)) {
|
|
sError = "No such file";
|
|
CUtils::PrintStatus(false, sError);
|
|
CUtils::PrintMessage(
|
|
"Restart ZNC with the --makeconf option if you wish to create this "
|
|
"config.");
|
|
return false;
|
|
}
|
|
|
|
if (!CFile::IsReg(m_sConfigFile)) {
|
|
sError = "Not a file";
|
|
CUtils::PrintStatus(false, sError);
|
|
return false;
|
|
}
|
|
|
|
CFile* pFile = new CFile(m_sConfigFile);
|
|
|
|
// need to open the config file Read/Write for fcntl()
|
|
// exclusive locking to work properly!
|
|
if (!pFile->Open(m_sConfigFile, O_RDWR)) {
|
|
sError = "Can not open config file";
|
|
CUtils::PrintStatus(false, sError);
|
|
delete pFile;
|
|
return false;
|
|
}
|
|
|
|
if (!pFile->TryExLock()) {
|
|
sError = "ZNC is already running on this config.";
|
|
CUtils::PrintStatus(false, sError);
|
|
delete pFile;
|
|
return false;
|
|
}
|
|
|
|
// (re)open the config file
|
|
delete m_pLockFile;
|
|
m_pLockFile = pFile;
|
|
CFile& File = *pFile;
|
|
|
|
if (!config.Parse(File, sError)) {
|
|
CUtils::PrintStatus(false, sError);
|
|
return false;
|
|
}
|
|
CUtils::PrintStatus(true);
|
|
|
|
// check if config is from old ZNC version and
|
|
// create a backup file if necessary
|
|
CString sSavedVersion;
|
|
config.FindStringEntry("version", sSavedVersion);
|
|
config.AddKeyValuePair("version", sSavedVersion);
|
|
if (sSavedVersion.empty()) {
|
|
CUtils::PrintError(
|
|
"Config does not contain a version identifier. It may be be too "
|
|
"old or corrupt.");
|
|
return false;
|
|
}
|
|
|
|
tuple<unsigned int, unsigned int> tSavedVersion =
|
|
make_tuple(sSavedVersion.Token(0, false, ".").ToUInt(),
|
|
sSavedVersion.Token(1, false, ".").ToUInt());
|
|
tuple<unsigned int, unsigned int> tCurrentVersion =
|
|
make_tuple(VERSION_MAJOR, VERSION_MINOR);
|
|
if (tSavedVersion < tCurrentVersion) {
|
|
CUtils::PrintMessage("Found old config from ZNC " + sSavedVersion +
|
|
". Saving a backup of it.");
|
|
BackupConfigOnce("pre-" + CString(VERSION_STR));
|
|
} else if (tSavedVersion > tCurrentVersion) {
|
|
CUtils::PrintError("Config was saved from ZNC " + sSavedVersion +
|
|
". It may or may not work with current ZNC " +
|
|
GetVersion());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CZNC::RehashConfig(CString& sError) {
|
|
ALLMODULECALL(OnPreRehash(), NOTHING);
|
|
|
|
CConfig config;
|
|
if (!ReadConfig(config, sError)) return false;
|
|
|
|
if (!LoadGlobal(config, sError)) return false;
|
|
|
|
// do not reload users - it's dangerous!
|
|
|
|
ALLMODULECALL(OnPostRehash(), NOTHING);
|
|
return true;
|
|
}
|
|
|
|
bool CZNC::LoadGlobal(CConfig& config, CString& sError) {
|
|
sError.clear();
|
|
|
|
CString sSavedVersion;
|
|
config.FindStringEntry("version", sSavedVersion);
|
|
tuple<unsigned int, unsigned int> tSavedVersion =
|
|
make_tuple(sSavedVersion.Token(0, false, ".").ToUInt(),
|
|
sSavedVersion.Token(1, false, ".").ToUInt());
|
|
|
|
MCString msModules; // Modules are queued for later loading
|
|
|
|
VCString vsList;
|
|
config.FindStringVector("loadmodule", vsList);
|
|
|
|
// Automatically load corecaps if config was upgraded from old version, but
|
|
// don't force it if user explicitly unloaded it
|
|
if (tSavedVersion < make_tuple(1, 9)) {
|
|
vsList.push_back("corecaps");
|
|
}
|
|
|
|
for (const CString& sModLine : vsList) {
|
|
CString sModName = sModLine.Token(0);
|
|
CString sArgs = sModLine.Token(1, true);
|
|
|
|
// compatibility for pre-1.0 configs
|
|
if (sModName == "saslauth" && tSavedVersion < make_tuple(0, 207)) {
|
|
CUtils::PrintMessage(
|
|
"saslauth module was renamed to cyrusauth. Loading cyrusauth "
|
|
"instead.");
|
|
sModName = "cyrusauth";
|
|
}
|
|
// end-compatibility for pre-1.0 configs
|
|
|
|
if (msModules.find(sModName) != msModules.end()) {
|
|
sError = "Module [" + sModName + "] already loaded";
|
|
CUtils::PrintError(sError);
|
|
return false;
|
|
}
|
|
CString sModRet;
|
|
CModule* pOldMod;
|
|
|
|
pOldMod = GetModules().FindModule(sModName);
|
|
if (!pOldMod) {
|
|
CUtils::PrintAction("Loading global module [" + sModName + "]");
|
|
|
|
bool bModRet =
|
|
GetModules().LoadModule(sModName, sArgs, CModInfo::GlobalModule,
|
|
nullptr, nullptr, sModRet);
|
|
|
|
CUtils::PrintStatus(bModRet, bModRet ? "" : sModRet);
|
|
if (!bModRet) {
|
|
sError = sModRet;
|
|
return false;
|
|
}
|
|
} else if (pOldMod->GetArgs() != sArgs) {
|
|
CUtils::PrintAction("Reloading global module [" + sModName + "]");
|
|
|
|
bool bModRet = GetModules().ReloadModule(sModName, sArgs, nullptr,
|
|
nullptr, sModRet);
|
|
|
|
CUtils::PrintStatus(bModRet, sModRet);
|
|
if (!bModRet) {
|
|
sError = sModRet;
|
|
return false;
|
|
}
|
|
} else
|
|
CUtils::PrintMessage("Module [" + sModName + "] already loaded.");
|
|
|
|
msModules[sModName] = sArgs;
|
|
}
|
|
|
|
m_vsMotd.clear();
|
|
config.FindStringVector("motd", vsList);
|
|
for (const CString& sMotd : vsList) {
|
|
AddMotd(sMotd);
|
|
}
|
|
|
|
if (config.FindStringVector("bindhost", vsList)) {
|
|
CUtils::PrintStatus(false,
|
|
"WARNING: the global BindHost list is deprecated. "
|
|
"Ignoring the following lines:");
|
|
for (const CString& sHost : vsList) {
|
|
CUtils::PrintStatus(false, "BindHost = " + sHost);
|
|
}
|
|
}
|
|
if (config.FindStringVector("vhost", vsList)) {
|
|
CUtils::PrintStatus(false,
|
|
"WARNING: the global vHost list is deprecated. "
|
|
"Ignoring the following lines:");
|
|
for (const CString& sHost : vsList) {
|
|
CUtils::PrintStatus(false, "vHost = " + sHost);
|
|
}
|
|
}
|
|
|
|
m_vsTrustedProxies.clear();
|
|
config.FindStringVector("trustedproxy", vsList);
|
|
for (const CString& sProxy : vsList) {
|
|
AddTrustedProxy(sProxy);
|
|
}
|
|
|
|
m_ssClientCapBlacklist.clear();
|
|
config.FindStringVector("disableclientcap", vsList);
|
|
for (const CString& sCap : vsList) {
|
|
m_ssClientCapBlacklist.insert(sCap);
|
|
}
|
|
m_ssServerCapBlacklist.clear();
|
|
config.FindStringVector("disableservercap", vsList);
|
|
for (const CString& sCap : vsList) {
|
|
m_ssServerCapBlacklist.insert(sCap);
|
|
}
|
|
|
|
CString sVal;
|
|
if (config.FindStringEntry("pidfile", sVal)) m_sPidFile = sVal;
|
|
if (config.FindStringEntry("statusprefix", sVal)) m_sStatusPrefix = sVal;
|
|
if (config.FindStringEntry("sslcertfile", sVal)) m_sSSLCertFile = sVal;
|
|
if (config.FindStringEntry("sslkeyfile", sVal)) m_sSSLKeyFile = sVal;
|
|
if (config.FindStringEntry("ssldhparamfile", sVal))
|
|
m_sSSLDHParamFile = sVal;
|
|
if (config.FindStringEntry("sslciphers", sVal)) m_sSSLCiphers = sVal;
|
|
if (config.FindStringEntry("skin", sVal)) SetSkinName(sVal);
|
|
if (config.FindStringEntry("connectdelay", sVal))
|
|
SetConnectDelay(sVal.ToUInt());
|
|
if (config.FindStringEntry("serverthrottle", sVal))
|
|
m_sConnectThrottle.SetTTL(sVal.ToUInt() * 1000);
|
|
if (config.FindStringEntry("anoniplimit", sVal))
|
|
m_uiAnonIPLimit = sVal.ToUInt();
|
|
if (config.FindStringEntry("maxbuffersize", sVal))
|
|
m_uiMaxBufferSize = sVal.ToUInt();
|
|
if (config.FindStringEntry("protectwebsessions", sVal))
|
|
m_bProtectWebSessions = sVal.ToBool();
|
|
if (config.FindStringEntry("hideversion", sVal))
|
|
m_bHideVersion = sVal.ToBool();
|
|
if (config.FindStringEntry("authonlyviamodule", sVal))
|
|
m_bAuthOnlyViaModule = sVal.ToBool();
|
|
if (config.FindStringEntry("sslprotocols", sVal)) {
|
|
if (!SetSSLProtocols(sVal)) {
|
|
VCString vsProtocols = GetAvailableSSLProtocols();
|
|
CUtils::PrintError("Invalid SSLProtocols value [" + sVal + "]");
|
|
CUtils::PrintError(
|
|
"The syntax is [SSLProtocols = [+|-]<protocol> ...]");
|
|
CUtils::PrintError(
|
|
"Available protocols are [" +
|
|
CString(", ").Join(vsProtocols.begin(), vsProtocols.end()) +
|
|
"]");
|
|
return false;
|
|
}
|
|
}
|
|
if (config.FindStringEntry("configwritedelay", sVal))
|
|
m_uiConfigWriteDelay = sVal.ToUInt();
|
|
|
|
UnloadRemovedModules(msModules);
|
|
|
|
if (!LoadListeners(config, sError)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CZNC::LoadUsers(CConfig& config, CString& sError) {
|
|
sError.clear();
|
|
|
|
m_msUsers.clear();
|
|
|
|
CConfig::SubConfig subConf;
|
|
config.FindSubConfig("user", subConf);
|
|
|
|
for (const auto& subIt : subConf) {
|
|
const CString& sUsername = subIt.first;
|
|
CConfig* pSubConf = subIt.second.m_pSubConfig;
|
|
|
|
CUtils::PrintMessage("Loading user [" + sUsername + "]");
|
|
|
|
std::unique_ptr<CUser> pUser(new CUser(sUsername));
|
|
|
|
if (!m_sStatusPrefix.empty()) {
|
|
if (!pUser->SetStatusPrefix(m_sStatusPrefix)) {
|
|
sError = "Invalid StatusPrefix [" + m_sStatusPrefix +
|
|
"] Must be 1-5 chars, no spaces.";
|
|
CUtils::PrintError(sError);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!pUser->ParseConfig(pSubConf, sError)) {
|
|
CUtils::PrintError(sError);
|
|
return false;
|
|
}
|
|
|
|
if (!pSubConf->empty()) {
|
|
sError = "Unhandled lines in config for User [" + sUsername + "]!";
|
|
CUtils::PrintError(sError);
|
|
DumpConfig(pSubConf);
|
|
return false;
|
|
}
|
|
|
|
CString sErr;
|
|
CUser* pRawUser = pUser.release();
|
|
if (!AddUser(pRawUser, sErr, true)) {
|
|
sError = "Invalid user [" + sUsername + "] " + sErr;
|
|
CUtils::PrintError(sError);
|
|
pRawUser->SetBeingDeleted(true);
|
|
delete pRawUser;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (m_msUsers.empty()) {
|
|
sError = "You must define at least one user in your config.";
|
|
CUtils::PrintError(sError);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CZNC::LoadListeners(CConfig& config, CString& sError) {
|
|
sError.clear();
|
|
|
|
// Delete all listeners
|
|
while (!m_vpListeners.empty()) {
|
|
delete m_vpListeners[0];
|
|
m_vpListeners.erase(m_vpListeners.begin());
|
|
}
|
|
|
|
// compatibility for pre-1.0 configs
|
|
const char* szListenerEntries[] = {"listen", "listen6", "listen4",
|
|
"listener", "listener6", "listener4"};
|
|
|
|
VCString vsList;
|
|
config.FindStringVector("loadmodule", vsList);
|
|
|
|
// This has to be after SSLCertFile is handled since it uses that value
|
|
for (const char* szEntry : szListenerEntries) {
|
|
config.FindStringVector(szEntry, vsList);
|
|
for (const CString& sListener : vsList) {
|
|
if (!AddListener(szEntry + CString(" ") + sListener, sError))
|
|
return false;
|
|
}
|
|
}
|
|
// end-compatibility for pre-1.0 configs
|
|
|
|
CConfig::SubConfig subConf;
|
|
config.FindSubConfig("listener", subConf);
|
|
|
|
for (const auto& subIt : subConf) {
|
|
CConfig* pSubConf = subIt.second.m_pSubConfig;
|
|
if (!AddListener(pSubConf, sError)) return false;
|
|
if (!pSubConf->empty()) {
|
|
sError = "Unhandled lines in Listener config!";
|
|
CUtils::PrintError(sError);
|
|
|
|
CZNC::DumpConfig(pSubConf);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (m_vpListeners.empty()) {
|
|
sError = "You must supply at least one Listener in your config.";
|
|
CUtils::PrintError(sError);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CZNC::UnloadRemovedModules(const MCString& msModules) {
|
|
// unload modules which are no longer in the config
|
|
|
|
set<CString> ssUnload;
|
|
for (CModule* pCurMod : GetModules()) {
|
|
if (msModules.find(pCurMod->GetModName()) == msModules.end())
|
|
ssUnload.insert(pCurMod->GetModName());
|
|
}
|
|
|
|
for (const CString& sMod : ssUnload) {
|
|
if (GetModules().UnloadModule(sMod))
|
|
CUtils::PrintMessage("Unloaded global module [" + sMod + "]");
|
|
else
|
|
CUtils::PrintMessage("Could not unload [" + sMod + "]");
|
|
}
|
|
}
|
|
|
|
void CZNC::DumpConfig(const CConfig* pConfig) {
|
|
CConfig::EntryMapIterator eit = pConfig->BeginEntries();
|
|
for (; eit != pConfig->EndEntries(); ++eit) {
|
|
const CString& sKey = eit->first;
|
|
const VCString& vsList = eit->second;
|
|
VCString::const_iterator it = vsList.begin();
|
|
for (; it != vsList.end(); ++it) {
|
|
CUtils::PrintError(sKey + " = " + *it);
|
|
}
|
|
}
|
|
|
|
CConfig::SubConfigMapIterator sit = pConfig->BeginSubConfigs();
|
|
for (; sit != pConfig->EndSubConfigs(); ++sit) {
|
|
const CString& sKey = sit->first;
|
|
const CConfig::SubConfig& sSub = sit->second;
|
|
CConfig::SubConfig::const_iterator it = sSub.begin();
|
|
|
|
for (; it != sSub.end(); ++it) {
|
|
CUtils::PrintError("SubConfig [" + sKey + " " + it->first + "]:");
|
|
DumpConfig(it->second.m_pSubConfig);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CZNC::ClearTrustedProxies() { m_vsTrustedProxies.clear(); }
|
|
|
|
bool CZNC::AddTrustedProxy(const CString& sHost) {
|
|
if (sHost.empty()) {
|
|
return false;
|
|
}
|
|
|
|
for (const CString& sTrustedProxy : m_vsTrustedProxies) {
|
|
if (sTrustedProxy.Equals(sHost)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
m_vsTrustedProxies.push_back(sHost);
|
|
return true;
|
|
}
|
|
|
|
bool CZNC::RemTrustedProxy(const CString& sHost) {
|
|
VCString::iterator it;
|
|
for (it = m_vsTrustedProxies.begin(); it != m_vsTrustedProxies.end();
|
|
++it) {
|
|
if (sHost.Equals(*it)) {
|
|
m_vsTrustedProxies.erase(it);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CZNC::Broadcast(const CString& sMessage, bool bAdminOnly, CUser* pSkipUser,
|
|
CClient* pSkipClient) {
|
|
for (const auto& it : m_msUsers) {
|
|
if (bAdminOnly && !it.second->IsAdmin()) continue;
|
|
|
|
if (it.second != pSkipUser) {
|
|
// TODO: translate message to user's language
|
|
CString sMsg = sMessage;
|
|
|
|
bool bContinue = false;
|
|
USERMODULECALL(OnBroadcast(sMsg), it.second, nullptr, &bContinue);
|
|
if (bContinue) continue;
|
|
|
|
it.second->PutStatusNotice("*** " + sMsg, nullptr, pSkipClient);
|
|
}
|
|
}
|
|
}
|
|
|
|
CModule* CZNC::FindModule(const CString& sModName, const CString& sUsername) {
|
|
if (sUsername.empty()) {
|
|
return CZNC::Get().GetModules().FindModule(sModName);
|
|
}
|
|
|
|
CUser* pUser = FindUser(sUsername);
|
|
|
|
return (!pUser) ? nullptr : pUser->GetModules().FindModule(sModName);
|
|
}
|
|
|
|
CModule* CZNC::FindModule(const CString& sModName, CUser* pUser) {
|
|
if (pUser) {
|
|
return pUser->GetModules().FindModule(sModName);
|
|
}
|
|
|
|
return CZNC::Get().GetModules().FindModule(sModName);
|
|
}
|
|
|
|
bool CZNC::UpdateModule(const CString& sModule) {
|
|
CModule* pModule;
|
|
|
|
map<CUser*, CString> musLoaded;
|
|
map<CIRCNetwork*, CString> mnsLoaded;
|
|
|
|
// Unload the module for every user and network
|
|
for (const auto& it : m_msUsers) {
|
|
CUser* pUser = it.second;
|
|
|
|
pModule = pUser->GetModules().FindModule(sModule);
|
|
if (pModule) {
|
|
musLoaded[pUser] = pModule->GetArgs();
|
|
pUser->GetModules().UnloadModule(sModule);
|
|
}
|
|
|
|
// See if the user has this module loaded to a network
|
|
vector<CIRCNetwork*> vNetworks = pUser->GetNetworks();
|
|
for (CIRCNetwork* pNetwork : vNetworks) {
|
|
pModule = pNetwork->GetModules().FindModule(sModule);
|
|
if (pModule) {
|
|
mnsLoaded[pNetwork] = pModule->GetArgs();
|
|
pNetwork->GetModules().UnloadModule(sModule);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unload the global module
|
|
bool bGlobal = false;
|
|
CString sGlobalArgs;
|
|
|
|
pModule = GetModules().FindModule(sModule);
|
|
if (pModule) {
|
|
bGlobal = true;
|
|
sGlobalArgs = pModule->GetArgs();
|
|
GetModules().UnloadModule(sModule);
|
|
}
|
|
|
|
// Lets reload everything
|
|
bool bError = false;
|
|
CString sErr;
|
|
|
|
// Reload the global module
|
|
if (bGlobal) {
|
|
if (!GetModules().LoadModule(sModule, sGlobalArgs,
|
|
CModInfo::GlobalModule, nullptr, nullptr,
|
|
sErr)) {
|
|
DEBUG("Failed to reload [" << sModule << "] globally [" << sErr
|
|
<< "]");
|
|
bError = true;
|
|
}
|
|
}
|
|
|
|
// Reload the module for all users
|
|
for (const auto& it : musLoaded) {
|
|
CUser* pUser = it.first;
|
|
const CString& sArgs = it.second;
|
|
|
|
if (!pUser->GetModules().LoadModule(
|
|
sModule, sArgs, CModInfo::UserModule, pUser, nullptr, sErr)) {
|
|
DEBUG("Failed to reload [" << sModule << "] for ["
|
|
<< pUser->GetUsername() << "] [" << sErr
|
|
<< "]");
|
|
bError = true;
|
|
}
|
|
}
|
|
|
|
// Reload the module for all networks
|
|
for (const auto& it : mnsLoaded) {
|
|
CIRCNetwork* pNetwork = it.first;
|
|
const CString& sArgs = it.second;
|
|
|
|
if (!pNetwork->GetModules().LoadModule(
|
|
sModule, sArgs, CModInfo::NetworkModule, pNetwork->GetUser(),
|
|
pNetwork, sErr)) {
|
|
DEBUG("Failed to reload ["
|
|
<< sModule << "] for [" << pNetwork->GetUser()->GetUsername()
|
|
<< "/" << pNetwork->GetName() << "] [" << sErr << "]");
|
|
bError = true;
|
|
}
|
|
}
|
|
|
|
return !bError;
|
|
}
|
|
|
|
CUser* CZNC::FindUser(const CString& sUsername) {
|
|
map<CString, CUser*>::iterator it = m_msUsers.find(sUsername);
|
|
|
|
if (it != m_msUsers.end()) {
|
|
return it->second;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool CZNC::DeleteUser(const CString& sUsername) {
|
|
CUser* pUser = FindUser(sUsername);
|
|
|
|
if (!pUser) {
|
|
return false;
|
|
}
|
|
|
|
m_msDelUsers[pUser->GetUsername()] = pUser;
|
|
return true;
|
|
}
|
|
|
|
bool CZNC::AddUser(CUser* pUser, CString& sErrorRet, bool bStartup) {
|
|
if (FindUser(pUser->GetUsername()) != nullptr) {
|
|
sErrorRet = t_s("User already exists");
|
|
DEBUG("User [" << pUser->GetUsername() << "] - already exists");
|
|
return false;
|
|
}
|
|
if (!pUser->IsValid(sErrorRet)) {
|
|
DEBUG("Invalid user [" << pUser->GetUsername() << "] - [" << sErrorRet
|
|
<< "]");
|
|
return false;
|
|
}
|
|
bool bFailed = false;
|
|
|
|
// do not call OnAddUser hook during ZNC startup
|
|
if (!bStartup) {
|
|
GLOBALMODULECALL(OnAddUser(*pUser, sErrorRet), &bFailed);
|
|
}
|
|
|
|
if (bFailed) {
|
|
DEBUG("AddUser [" << pUser->GetUsername() << "] aborted by a module ["
|
|
<< sErrorRet << "]");
|
|
return false;
|
|
}
|
|
m_msUsers[pUser->GetUsername()] = pUser;
|
|
return true;
|
|
}
|
|
|
|
CListener* CZNC::FindListener(u_short uPort, const CString& sBindHost,
|
|
EAddrType eAddr) {
|
|
for (CListener* pListener : m_vpListeners) {
|
|
CTCPListener* pTCPListener = dynamic_cast<CTCPListener*>(pListener);
|
|
if (!pTCPListener) continue;
|
|
if (pTCPListener->GetPort() != uPort) continue;
|
|
if (pTCPListener->GetBindHost() != sBindHost) continue;
|
|
if (pTCPListener->GetAddrType() != eAddr) continue;
|
|
return pListener;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
CListener* CZNC::FindUnixListener(const CString& sPath) {
|
|
for (CListener* pListener : m_vpListeners) {
|
|
CUnixListener* pUnixListener = dynamic_cast<CUnixListener*>(pListener);
|
|
if (!pUnixListener) continue;
|
|
if (pUnixListener->GetPath() != sPath) continue;
|
|
return pListener;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool CZNC::AddListener(const CString& sLine, CString& sError) {
|
|
CString sName = sLine.Token(0);
|
|
CString sValue = sLine.Token(1, true);
|
|
|
|
EAddrType eAddr = ADDR_ALL;
|
|
if (sName.Equals("Listen4") || sName.Equals("Listen") ||
|
|
sName.Equals("Listener4")) {
|
|
eAddr = ADDR_IPV4ONLY;
|
|
}
|
|
if (sName.Equals("Listener6")) {
|
|
eAddr = ADDR_IPV6ONLY;
|
|
}
|
|
|
|
CListener::EAcceptType eAccept = CListener::ACCEPT_ALL;
|
|
if (sValue.TrimPrefix("irc_only "))
|
|
eAccept = CListener::ACCEPT_IRC;
|
|
else if (sValue.TrimPrefix("web_only "))
|
|
eAccept = CListener::ACCEPT_HTTP;
|
|
|
|
bool bSSL = false;
|
|
CString sPort;
|
|
CString sBindHost;
|
|
|
|
if (ADDR_IPV4ONLY == eAddr) {
|
|
sValue.Replace(":", " ");
|
|
}
|
|
|
|
if (sValue.Contains(" ")) {
|
|
sBindHost = sValue.Token(0, false, " ");
|
|
sPort = sValue.Token(1, true, " ");
|
|
} else {
|
|
sPort = sValue;
|
|
}
|
|
|
|
if (sPort.TrimPrefix("+")) {
|
|
bSSL = true;
|
|
}
|
|
|
|
// No support for URIPrefix for old-style configs.
|
|
CString sURIPrefix;
|
|
unsigned short uPort = sPort.ToUShort();
|
|
return AddListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept,
|
|
sError);
|
|
}
|
|
|
|
bool CZNC::CheckSslAndPemFile(bool bSSL, CString& sError) {
|
|
#ifndef HAVE_LIBSSL
|
|
if (bSSL) {
|
|
sError = t_s("SSL is not enabled");
|
|
CUtils::PrintStatus(false, sError);
|
|
return false;
|
|
}
|
|
#else
|
|
CString sPemFile = GetPemLocation();
|
|
|
|
if (bSSL && !CFile::Exists(sPemFile)) {
|
|
sError = t_f("Unable to locate pem file: {1}")(sPemFile);
|
|
CUtils::PrintStatus(false, sError);
|
|
|
|
// If stdin is e.g. /dev/null and we call GetBoolInput(),
|
|
// we are stuck in an endless loop!
|
|
if (isatty(0) &&
|
|
CUtils::GetBoolInput("Would you like to create a new pem file?",
|
|
true)) {
|
|
sError.clear();
|
|
WritePemFile();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool CZNC::AddTCPListener(unsigned short uPort, const CString& sBindHost,
|
|
const CString& sURIPrefix, bool bSSL, EAddrType eAddr,
|
|
CListener::EAcceptType eAccept, CString& sError) {
|
|
CString sHostComment;
|
|
|
|
if (!sBindHost.empty()) {
|
|
sHostComment = " on host [" + sBindHost + "]";
|
|
}
|
|
|
|
CString sIPV6Comment;
|
|
|
|
switch (eAddr) {
|
|
case ADDR_ALL:
|
|
sIPV6Comment = "";
|
|
break;
|
|
case ADDR_IPV4ONLY:
|
|
sIPV6Comment = " using ipv4";
|
|
break;
|
|
case ADDR_IPV6ONLY:
|
|
sIPV6Comment = " using ipv6";
|
|
}
|
|
|
|
CUtils::PrintAction("Binding to port [" + CString((bSSL) ? "+" : "") +
|
|
CString(uPort) + "]" + sHostComment + sIPV6Comment);
|
|
|
|
#ifndef HAVE_IPV6
|
|
if (ADDR_IPV6ONLY == eAddr) {
|
|
sError = t_s("IPv6 is not enabled");
|
|
CUtils::PrintStatus(false, sError);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if (!CheckSslAndPemFile(bSSL, sError)) return false;
|
|
|
|
if (!uPort) {
|
|
sError = t_s("Invalid port");
|
|
CUtils::PrintStatus(false, sError);
|
|
return false;
|
|
}
|
|
|
|
CListener* pListener =
|
|
new CTCPListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept);
|
|
return FinishAddingListener(pListener, sError);
|
|
}
|
|
|
|
bool CZNC::AddUnixListener(const CString& sPath, const CString& sURIPrefix,
|
|
bool bSSL, CListener::EAcceptType eAccept,
|
|
CString& sError) {
|
|
CUtils::PrintAction("Binding to path [" + sPath + "]" + (bSSL ? " with SSL" : ""));
|
|
|
|
if (!CheckSslAndPemFile(bSSL, sError)) return false;
|
|
|
|
CListener* pListener =
|
|
new CUnixListener(sPath, sURIPrefix, bSSL, eAccept);
|
|
return FinishAddingListener(pListener, sError);
|
|
}
|
|
|
|
bool CZNC::FinishAddingListener(CListener* pListener, CString& sError) {
|
|
if (!pListener->Listen()) {
|
|
sError = FormatBindError();
|
|
CUtils::PrintStatus(false, sError);
|
|
delete pListener;
|
|
return false;
|
|
}
|
|
|
|
m_vpListeners.push_back(pListener);
|
|
CUtils::PrintStatus(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CZNC::AddListener(CConfig* pConfig, CString& sError) {
|
|
CString sBindHost;
|
|
CString sURIPrefix;
|
|
CString sPath;
|
|
bool bSSL;
|
|
bool b4;
|
|
#ifdef HAVE_IPV6
|
|
bool b6 = true;
|
|
#else
|
|
bool b6 = false;
|
|
#endif
|
|
bool bIRC;
|
|
bool bWeb;
|
|
unsigned short uPort;
|
|
bool bTcpListener = true;
|
|
|
|
if (!pConfig->FindUShortEntry("port", uPort)) {
|
|
bTcpListener = false;
|
|
if (!pConfig->FindStringEntry("path", sPath)) {
|
|
sError = "No port and no path given";
|
|
CUtils::PrintError(sError);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
pConfig->FindBoolEntry("ssl", bSSL, false);
|
|
pConfig->FindBoolEntry("allowirc", bIRC, true);
|
|
pConfig->FindBoolEntry("allowweb", bWeb, true);
|
|
pConfig->FindStringEntry("uriprefix", sURIPrefix);
|
|
|
|
CListener::EAcceptType eAccept;
|
|
if (bIRC && bWeb) {
|
|
eAccept = CListener::ACCEPT_ALL;
|
|
} else if (bIRC && !bWeb) {
|
|
eAccept = CListener::ACCEPT_IRC;
|
|
} else if (!bIRC && bWeb) {
|
|
eAccept = CListener::ACCEPT_HTTP;
|
|
} else {
|
|
sError = "Either Web or IRC or both should be selected";
|
|
CUtils::PrintError(sError);
|
|
return false;
|
|
}
|
|
|
|
// URIPrefix must start with a slash and end without one.
|
|
if (!sURIPrefix.empty()) {
|
|
if (!sURIPrefix.StartsWith("/")) {
|
|
sURIPrefix = "/" + sURIPrefix;
|
|
}
|
|
if (sURIPrefix.EndsWith("/")) {
|
|
sURIPrefix.TrimRight("/");
|
|
}
|
|
}
|
|
|
|
if (bTcpListener) {
|
|
pConfig->FindStringEntry("host", sBindHost);
|
|
pConfig->FindBoolEntry("ipv4", b4, true);
|
|
pConfig->FindBoolEntry("ipv6", b6, b6);
|
|
|
|
EAddrType eAddr;
|
|
if (b4 && b6) {
|
|
eAddr = ADDR_ALL;
|
|
} else if (b4 && !b6) {
|
|
eAddr = ADDR_IPV4ONLY;
|
|
} else if (!b4 && b6) {
|
|
eAddr = ADDR_IPV6ONLY;
|
|
} else {
|
|
sError = "No address family given";
|
|
CUtils::PrintError(sError);
|
|
return false;
|
|
}
|
|
|
|
return AddTCPListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr,
|
|
eAccept, sError);
|
|
}
|
|
return AddUnixListener(sPath, sURIPrefix, bSSL, eAccept, sError);
|
|
}
|
|
|
|
bool CZNC::AddListener(CListener* pListener) {
|
|
if (!pListener->GetRealListener()) {
|
|
// Listener doesn't actually listen
|
|
delete pListener;
|
|
return false;
|
|
}
|
|
|
|
// We don't check if there is an identical listener already listening
|
|
// since one can't listen on e.g. the same port multiple times
|
|
|
|
m_vpListeners.push_back(pListener);
|
|
return true;
|
|
}
|
|
|
|
bool CZNC::DelListener(CListener* pListener) {
|
|
auto it = std::find(m_vpListeners.begin(), m_vpListeners.end(), pListener);
|
|
if (it != m_vpListeners.end()) {
|
|
m_vpListeners.erase(it);
|
|
delete pListener;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
CString CZNC::FormatBindError() {
|
|
CString sError = (errno == 0 ? t_s(("unknown error, check the host name"))
|
|
: CString(strerror(errno)));
|
|
return t_f("Unable to bind: {1}")(sError);
|
|
}
|
|
|
|
static CZNC* s_pZNC = nullptr;
|
|
|
|
void CZNC::CreateInstance() {
|
|
if (s_pZNC) abort();
|
|
|
|
s_pZNC = new CZNC();
|
|
}
|
|
|
|
CZNC& CZNC::Get() { return *s_pZNC; }
|
|
|
|
void CZNC::DestroyInstance() {
|
|
delete s_pZNC;
|
|
s_pZNC = nullptr;
|
|
}
|
|
|
|
CZNC::TrafficStatsMap CZNC::GetTrafficStats(TrafficStatsPair& Users,
|
|
TrafficStatsPair& ZNC,
|
|
TrafficStatsPair& Total) {
|
|
TrafficStatsMap ret;
|
|
unsigned long long uiUsers_in, uiUsers_out, uiZNC_in, uiZNC_out;
|
|
const map<CString, CUser*>& msUsers = CZNC::Get().GetUserMap();
|
|
|
|
uiUsers_in = uiUsers_out = 0;
|
|
uiZNC_in = BytesRead();
|
|
uiZNC_out = BytesWritten();
|
|
|
|
for (const auto& it : msUsers) {
|
|
ret[it.first] =
|
|
TrafficStatsPair(it.second->BytesRead(), it.second->BytesWritten());
|
|
uiUsers_in += it.second->BytesRead();
|
|
uiUsers_out += it.second->BytesWritten();
|
|
}
|
|
|
|
for (Csock* pSock : m_Manager) {
|
|
CUser* pUser = nullptr;
|
|
if (pSock->GetSockName().StartsWith("IRC::")) {
|
|
pUser = ((CIRCSock*)pSock)->GetNetwork()->GetUser();
|
|
} else if (pSock->GetSockName().StartsWith("USR::")) {
|
|
pUser = ((CClient*)pSock)->GetUser();
|
|
}
|
|
|
|
if (pUser) {
|
|
ret[pUser->GetUsername()].first += pSock->GetBytesRead();
|
|
ret[pUser->GetUsername()].second += pSock->GetBytesWritten();
|
|
uiUsers_in += pSock->GetBytesRead();
|
|
uiUsers_out += pSock->GetBytesWritten();
|
|
} else {
|
|
uiZNC_in += pSock->GetBytesRead();
|
|
uiZNC_out += pSock->GetBytesWritten();
|
|
}
|
|
}
|
|
|
|
Users = TrafficStatsPair(uiUsers_in, uiUsers_out);
|
|
ZNC = TrafficStatsPair(uiZNC_in, uiZNC_out);
|
|
Total = TrafficStatsPair(uiUsers_in + uiZNC_in, uiUsers_out + uiZNC_out);
|
|
|
|
return ret;
|
|
}
|
|
|
|
CZNC::TrafficStatsMap CZNC::GetNetworkTrafficStats(const CString& sUsername,
|
|
TrafficStatsPair& Total) {
|
|
TrafficStatsMap Networks;
|
|
|
|
CUser* pUser = FindUser(sUsername);
|
|
if (pUser) {
|
|
for (const CIRCNetwork* pNetwork : pUser->GetNetworks()) {
|
|
Networks[pNetwork->GetName()].first = pNetwork->BytesRead();
|
|
Networks[pNetwork->GetName()].second = pNetwork->BytesWritten();
|
|
Total.first += pNetwork->BytesRead();
|
|
Total.second += pNetwork->BytesWritten();
|
|
}
|
|
|
|
for (Csock* pSock : m_Manager) {
|
|
CIRCNetwork* pNetwork = nullptr;
|
|
if (pSock->GetSockName().StartsWith("IRC::")) {
|
|
pNetwork = ((CIRCSock*)pSock)->GetNetwork();
|
|
} else if (pSock->GetSockName().StartsWith("USR::")) {
|
|
pNetwork = ((CClient*)pSock)->GetNetwork();
|
|
}
|
|
|
|
if (pNetwork && pNetwork->GetUser() == pUser) {
|
|
Networks[pNetwork->GetName()].first = pSock->GetBytesRead();
|
|
Networks[pNetwork->GetName()].second = pSock->GetBytesWritten();
|
|
Total.first += pSock->GetBytesRead();
|
|
Total.second += pSock->GetBytesWritten();
|
|
}
|
|
}
|
|
}
|
|
|
|
return Networks;
|
|
}
|
|
|
|
void CZNC::AuthUser(std::shared_ptr<CAuthBase> AuthClass) {
|
|
// TODO unless the auth module calls it, CUser::IsHostAllowed() is not
|
|
// honoured
|
|
bool bReturn = false;
|
|
GLOBALMODULECALL(OnLoginAttempt(AuthClass), &bReturn);
|
|
if (bReturn) return;
|
|
|
|
CUser* pUser = FindUser(AuthClass->GetUsername());
|
|
|
|
if (!pUser || !pUser->CheckPass(AuthClass->GetPassword())) {
|
|
AuthClass->RefuseLogin("Invalid Password");
|
|
return;
|
|
}
|
|
|
|
CString sHost = AuthClass->GetRemoteIP();
|
|
|
|
if (!pUser->IsHostAllowed(sHost)) {
|
|
AuthClass->RefuseLogin("Your host [" + sHost + "] is not allowed");
|
|
return;
|
|
}
|
|
|
|
AuthClass->AcceptLogin(*pUser);
|
|
}
|
|
|
|
class CConnectQueueTimer : public CCron {
|
|
public:
|
|
CConnectQueueTimer(int iSecs) : CCron() {
|
|
SetName("Connect users");
|
|
Start(iSecs);
|
|
// Don't wait iSecs seconds for first timer run
|
|
m_bRunOnNextCall = true;
|
|
}
|
|
~CConnectQueueTimer() override {
|
|
// This is only needed when ZNC shuts down:
|
|
// CZNC::~CZNC() sets its CConnectQueueTimer pointer to nullptr and
|
|
// calls the manager's Cleanup() which destroys all sockets and
|
|
// timers. If something calls CZNC::EnableConnectQueue() here
|
|
// (e.g. because a CIRCSock is destroyed), the socket manager
|
|
// deletes that timer almost immediately, but CZNC now got a
|
|
// dangling pointer to this timer which can crash later on.
|
|
//
|
|
// Unlikely but possible ;)
|
|
CZNC::Get().LeakConnectQueueTimer(this);
|
|
}
|
|
|
|
protected:
|
|
void RunJob() override {
|
|
list<CIRCNetwork*> ConnectionQueue;
|
|
list<CIRCNetwork*>& RealConnectionQueue =
|
|
CZNC::Get().GetConnectionQueue();
|
|
|
|
// Problem: If a network can't connect right now because e.g. it
|
|
// is throttled, it will re-insert itself into the connection
|
|
// queue. However, we must only give each network a single
|
|
// chance during this timer run.
|
|
//
|
|
// Solution: We move the connection queue to our local list at
|
|
// the beginning and work from that.
|
|
ConnectionQueue.swap(RealConnectionQueue);
|
|
|
|
while (!ConnectionQueue.empty()) {
|
|
CIRCNetwork* pNetwork = ConnectionQueue.front();
|
|
ConnectionQueue.pop_front();
|
|
|
|
if (pNetwork->Connect()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Now re-insert anything that is left in our local list into
|
|
* the real connection queue.
|
|
*/
|
|
RealConnectionQueue.splice(RealConnectionQueue.begin(),
|
|
ConnectionQueue);
|
|
|
|
if (RealConnectionQueue.empty()) {
|
|
DEBUG("ConnectQueueTimer done");
|
|
CZNC::Get().DisableConnectQueue();
|
|
}
|
|
}
|
|
};
|
|
|
|
void CZNC::SetConnectDelay(unsigned int i) {
|
|
if (i < 1) {
|
|
// Don't hammer server with our failed connects
|
|
i = 1;
|
|
}
|
|
if (m_uiConnectDelay != i && m_pConnectQueueTimer != nullptr) {
|
|
m_pConnectQueueTimer->Start(i);
|
|
}
|
|
m_uiConnectDelay = i;
|
|
}
|
|
|
|
VCString CZNC::GetAvailableSSLProtocols() {
|
|
// NOTE: keep in sync with SetSSLProtocols()
|
|
return {"SSLv2", "SSLv3", "TLSv1", "TLSV1.1", "TLSv1.2"};
|
|
}
|
|
|
|
bool CZNC::SetSSLProtocols(const CString& sProtocols) {
|
|
VCString vsProtocols;
|
|
sProtocols.Split(" ", vsProtocols, false, "", "", true, true);
|
|
|
|
unsigned int uDisabledProtocols = Csock::EDP_SSL;
|
|
for (CString& sProtocol : vsProtocols) {
|
|
unsigned int uFlag = 0;
|
|
bool bEnable = sProtocol.TrimPrefix("+");
|
|
bool bDisable = sProtocol.TrimPrefix("-");
|
|
|
|
// NOTE: keep in sync with GetAvailableSSLProtocols()
|
|
if (sProtocol.Equals("All")) {
|
|
uFlag = ~0;
|
|
} else if (sProtocol.Equals("SSLv2")) {
|
|
uFlag = Csock::EDP_SSLv2;
|
|
} else if (sProtocol.Equals("SSLv3")) {
|
|
uFlag = Csock::EDP_SSLv3;
|
|
} else if (sProtocol.Equals("TLSv1")) {
|
|
uFlag = Csock::EDP_TLSv1;
|
|
} else if (sProtocol.Equals("TLSv1.1")) {
|
|
uFlag = Csock::EDP_TLSv1_1;
|
|
} else if (sProtocol.Equals("TLSv1.2")) {
|
|
uFlag = Csock::EDP_TLSv1_2;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (bEnable) {
|
|
uDisabledProtocols &= ~uFlag;
|
|
} else if (bDisable) {
|
|
uDisabledProtocols |= uFlag;
|
|
} else {
|
|
uDisabledProtocols = ~uFlag;
|
|
}
|
|
}
|
|
|
|
m_sSSLProtocols = sProtocols;
|
|
m_uDisabledSSLProtocols = uDisabledProtocols;
|
|
return true;
|
|
}
|
|
|
|
void CZNC::EnableConnectQueue() {
|
|
if (!m_pConnectQueueTimer && !m_uiConnectPaused &&
|
|
!m_lpConnectQueue.empty()) {
|
|
m_pConnectQueueTimer = new CConnectQueueTimer(m_uiConnectDelay);
|
|
GetManager().AddCron(m_pConnectQueueTimer);
|
|
}
|
|
}
|
|
|
|
void CZNC::DisableConnectQueue() {
|
|
if (m_pConnectQueueTimer) {
|
|
// This will kill the cron
|
|
m_pConnectQueueTimer->Stop();
|
|
m_pConnectQueueTimer = nullptr;
|
|
}
|
|
}
|
|
|
|
void CZNC::PauseConnectQueue() {
|
|
DEBUG("Connection queue paused");
|
|
m_uiConnectPaused++;
|
|
|
|
if (m_pConnectQueueTimer) {
|
|
m_pConnectQueueTimer->Pause();
|
|
}
|
|
}
|
|
|
|
void CZNC::ResumeConnectQueue() {
|
|
DEBUG("Connection queue resumed");
|
|
m_uiConnectPaused--;
|
|
|
|
EnableConnectQueue();
|
|
if (m_pConnectQueueTimer) {
|
|
m_pConnectQueueTimer->UnPause();
|
|
}
|
|
}
|
|
|
|
void CZNC::ForceEncoding() {
|
|
m_uiForceEncoding++;
|
|
#ifdef HAVE_ICU
|
|
for (Csock* pSock : GetManager()) {
|
|
pSock->SetEncoding(FixupEncoding(pSock->GetEncoding()));
|
|
}
|
|
#endif
|
|
}
|
|
void CZNC::UnforceEncoding() { m_uiForceEncoding--; }
|
|
bool CZNC::IsForcingEncoding() const { return m_uiForceEncoding; }
|
|
CString CZNC::FixupEncoding(const CString& sEncoding) const {
|
|
if (!m_uiForceEncoding) {
|
|
return sEncoding;
|
|
}
|
|
if (sEncoding.empty()) {
|
|
return "UTF-8";
|
|
}
|
|
const char* sRealEncoding = sEncoding.c_str();
|
|
if (sEncoding[0] == '*' || sEncoding[0] == '^') {
|
|
sRealEncoding++;
|
|
}
|
|
if (!*sRealEncoding) {
|
|
return "UTF-8";
|
|
}
|
|
#ifdef HAVE_ICU
|
|
UErrorCode e = U_ZERO_ERROR;
|
|
UConverter* cnv = ucnv_open(sRealEncoding, &e);
|
|
if (cnv) {
|
|
ucnv_close(cnv);
|
|
}
|
|
if (U_FAILURE(e)) {
|
|
return "UTF-8";
|
|
}
|
|
#endif
|
|
return sEncoding;
|
|
}
|
|
|
|
void CZNC::AddNetworkToQueue(CIRCNetwork* pNetwork) {
|
|
// Make sure we are not already in the queue
|
|
if (std::find(m_lpConnectQueue.begin(), m_lpConnectQueue.end(), pNetwork) !=
|
|
m_lpConnectQueue.end()) {
|
|
return;
|
|
}
|
|
|
|
m_lpConnectQueue.push_back(pNetwork);
|
|
EnableConnectQueue();
|
|
}
|
|
|
|
void CZNC::LeakConnectQueueTimer(CConnectQueueTimer* pTimer) {
|
|
if (m_pConnectQueueTimer == pTimer) m_pConnectQueueTimer = nullptr;
|
|
}
|
|
|
|
bool CZNC::WaitForChildLock() { return m_pLockFile && m_pLockFile->ExLock(); }
|
|
|
|
void CZNC::DisableConfigTimer() {
|
|
if (m_pConfigTimer) {
|
|
m_pConfigTimer->Stop();
|
|
m_pConfigTimer = nullptr;
|
|
}
|
|
}
|