/* * Copyright (C) 2004-2020 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 #include #include #include #include #include #include #include #include #include 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_pLockFile(nullptr), m_uiConnectDelay(5), m_uiAnonIPLimit(10), m_uiMaxBufferSize(500), m_uDisabledSSLProtocols(Csock::EDP_SSL), 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& 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 ? "https://znc.in" : "https://znc.in"; if (!bIncludeVersion) { return "ZNC - " + sAddress; } CString sVersion = GetVersion(); return "ZNC " + sVersion + " - " + sAddress; } CString CZNC::GetCompileOptionsString() { // Build system doesn't affect ABI return ZNC_COMPILE_OPTIONS_STRING + CString( ", build: " #ifdef BUILD_WITH_CMAKE "cmake" #else "autoconf" #endif ); } 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) { CConfig listenerConfig; listenerConfig.AddKeyValuePair("Host", pListener->GetBindHost()); listenerConfig.AddKeyValuePair("URIPrefix", pListener->GetURIPrefix() + "/"); listenerConfig.AddKeyValuePair("Port", CString(pListener->GetPort())); listenerConfig.AddKeyValuePair( "IPv4", CString(pListener->GetAddrType() != ADDR_IPV6ONLY)); listenerConfig.AddKeyValuePair( "IPv6", CString(pListener->GetAddrType() != ADDR_IPV4ONLY)); listenerConfig.AddKeyValuePair("SSL", CString(pListener->IsSSL())); listenerConfig.AddKeyValuePair( "AllowIRC", CString(pListener->GetAcceptType() != CListener::ACCEPT_HTTP)); listenerConfig.AddKeyValuePair( "AllowWeb", CString(pListener->GetAcceptType() != CListener::ACCEPT_IRC)); config.AddSubConfig("Listener", "listener" + CString(l++), listenerConfig); } 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; do { bSuccess = true; while (true) { if (!CUtils::GetNumInput("Listen on port", uListenPort, 1025, 65534)) { continue; } if (uListenPort == 6667) { CUtils::PrintStatus(false, "WARNING: Some web browsers reject port " "6667. If you intend to"); CUtils::PrintStatus(false, "use ZNC's web interface, you might want " "to use another port."); if (!CUtils::GetBoolInput("Proceed with port 6667 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 CListener( (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(""); 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(""); // !Listen set ssGlobalMods; GetModules().GetDefaultMods(ssGlobalMods, CModInfo::GlobalModule); vector 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(""); CString sSalt; sAnswer = CUtils::GetSaltedHashPass(sSalt); vsLines.push_back("\tPass = " + CUtils::sDefaultHash + "#" + sAnswer + "#" + sSalt + "#"); 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 ssUserMods; GetModules().GetDefaultMods(ssUserMods, CModInfo::UserModule); vector 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, "freenode"); } while (!CIRCNetwork::IsValidNetwork(sNetwork)); vsLines.push_back("\t"); set ssNetworkMods; GetModules().GetDefaultMods(ssNetworkMods, CModInfo::NetworkModule); vector 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("freenode")) { sHost = "chat.freenode.net"; #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); 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"); vsLines.push_back("\t\t"); } } CUtils::PrintMessage("Enabled network modules [" + CString(", ").Join(vsNetworkModNames.begin(), vsNetworkModNames.end()) + "]"); vsLines.push_back("\t"); } vsLines.push_back(""); 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 " + sSSL + CString(uListenPort) + " " + sUser + ":", true); CUtils::PrintMessage(""); CUtils::PrintMessage( "To manage settings, users and networks, point your web browser to", true); CUtils::PrintMessage( sProtocol + "://:" + 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); if (sSavedVersion.empty()) { CUtils::PrintError( "Config does not contain a version identifier. It may be be too " "old or corrupt."); return false; } tuple tSavedVersion = make_tuple(sSavedVersion.Token(0, false, ".").ToUInt(), sSavedVersion.Token(1, false, ".").ToUInt()); tuple 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(); MCString msModules; // Modules are queued for later loading VCString vsList; config.FindStringVector("loadmodule", vsList); for (const CString& sModLine : vsList) { CString sModName = sModLine.Token(0); CString sArgs = sModLine.Token(1, true); // compatibility for pre-1.0 configs CString sSavedVersion; config.FindStringEntry("version", sSavedVersion); tuple tSavedVersion = make_tuple(sSavedVersion.Token(0, false, ".").ToUInt(), sSavedVersion.Token(1, false, ".").ToUInt()); 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); } 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 = [+|-] ...]"); 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 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 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 musLoaded; map 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 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::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) { if (pListener->GetPort() != uPort) continue; if (pListener->GetBindHost() != sBindHost) continue; if (pListener->GetAddrType() != eAddr) 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::AddListener(unsigned short uPort, const CString& sBindHost, const CString& sURIPrefixRaw, 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 #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; } CUtils::PrintAction("Binding to port [+" + CString(uPort) + "]" + sHostComment + sIPV6Comment); } #endif if (!uPort) { sError = t_s("Invalid port"); CUtils::PrintStatus(false, sError); return false; } // URIPrefix must start with a slash and end without one. CString sURIPrefix = CString(sURIPrefixRaw); if (!sURIPrefix.empty()) { if (!sURIPrefix.StartsWith("/")) { sURIPrefix = "/" + sURIPrefix; } if (sURIPrefix.EndsWith("/")) { sURIPrefix.TrimRight("/"); } } CListener* pListener = new CListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, eAccept); 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; bool bSSL; bool b4; #ifdef HAVE_IPV6 bool b6 = true; #else bool b6 = false; #endif bool bIRC; bool bWeb; unsigned short uPort; if (!pConfig->FindUShortEntry("port", uPort)) { sError = "No port given"; CUtils::PrintError(sError); return false; } pConfig->FindStringEntry("host", sBindHost); pConfig->FindBoolEntry("ssl", bSSL, false); pConfig->FindBoolEntry("ipv4", b4, true); pConfig->FindBoolEntry("ipv6", b6, b6); pConfig->FindBoolEntry("allowirc", bIRC, true); pConfig->FindBoolEntry("allowweb", bWeb, true); pConfig->FindStringEntry("uriprefix", sURIPrefix); 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; } 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; } return AddListener(uPort, sBindHost, sURIPrefix, bSSL, eAddr, 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& 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 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 ConnectionQueue; list& 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; } }