/* * Copyright (C) 2004-2015 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 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; static inline CString FormatBindError() { CString sError = (errno == 0 ? CString("unknown error, check the host name") : CString(strerror(errno))); return "Unable to bind [" + sError + "]"; } CZNC::CZNC() { if (!InitCsocket()) { CUtils::PrintError("Could not initialize Csocket!"); exit(-1); } m_pModules = new CModules(); m_uiConnectDelay = 5; m_uiAnonIPLimit = 10; m_uBytesRead = 0; m_uBytesWritten = 0; m_uiMaxBufferSize = 500; m_pConnectQueueTimer = NULL; m_uiConnectPaused = 0; m_eConfigState = ECONFIG_NOTHING; m_TimeStarted = time(NULL); m_sConnectThrottle.SetTTL(30000); m_pLockFile = NULL; m_bProtectWebSessions = true; m_bHideVersion = false; m_uDisabledSSLProtocols = Csock::EDP_SSL; m_sSSLProtocols = ""; } CZNC::~CZNC() { m_pModules->UnloadAll(); for (map::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) { a->second->GetModules().UnloadAll(); const vector& networks = a->second->GetNetworks(); for (vector::const_iterator b = networks.begin(); b != networks.end(); ++b) { (*b)->GetModules().UnloadAll(); } } for (size_t b = 0; b < m_vpListeners.size(); b++) { delete m_vpListeners[b]; } for (map::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) { a->second->SetBeingDeleted(true); } m_pConnectQueueTimer = NULL; // 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 ? "http://znc.in" : "http://znc.in"; if (!bIncludeVersion) { return "ZNC - " + sAddress; } CString sVersion = GetVersion(); return "ZNC - " + sVersion + " - " + sAddress; } CString CZNC::GetCompileOptionsString() { return "IPv6: " #ifdef HAVE_IPV6 "yes" #else "no" #endif ", SSL: " #ifdef HAVE_LIBSSL "yes" #else "no" #endif ", DNS: " #ifdef HAVE_THREADED_DNS "threads" #else "blocking" #endif ", charset: " #ifdef HAVE_ICU "yes" #else "no" #endif ; } CString CZNC::GetUptime() const { time_t now = time(NULL); return CString::ToTimeStr(now - TimeStarted()); } bool CZNC::OnBoot() { bool bFail = false; ALLMODULECALL(OnBoot(), &bFail); if (bFail) return false; return true; } bool CZNC::HandleUserDeletion() { map::iterator it; map::iterator end; if (m_msDelUsers.empty()) return false; end = m_msDelUsers.end(); for (it = m_msDelUsers.begin(); it != end; ++it) { 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; } 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_NEED_WRITE: case ECONFIG_NEED_VERBOSE_WRITE: SetConfigState(ECONFIG_NOTHING); 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; } // 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 600 sec m_Manager.DynamicSelectLoop(100 * 1000, 600 * 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 NULL; } bool CZNC::WritePidFile(int iPid) { CFile* File = InitPidFile(); if (File == NULL) 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 == NULL) 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 (map::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) { a->second->SetBeingDeleted(true); delete a->second; } m_msUsers.clear(); DisableConnectQueue(); } bool CZNC::IsHostAllowed(const CString& sHostMask) const { for (map::const_iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) { if (a->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::ExpandConfigPath(const CString& sConfigFile, bool bAllowMkDir) { CString sRetPath; if (sConfigFile.empty()) { sRetPath = GetConfPath(bAllowMkDir) + "/znc.conf"; } else { if (sConfigFile.Left(2) == "./" || sConfigFile.Left(3) == "../") { sRetPath = GetCurPath() + "/" + sConfigFile; } else if (sConfigFile.Left(1) != "/") { 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(m_sSSLCertFile)); config.AddKeyValuePair("ProtectWebSessions", CString(m_bProtectWebSessions)); config.AddKeyValuePair("HideVersion", CString(m_bHideVersion)); config.AddKeyValuePair("Version", CString(VERSION_STR)); for (size_t l = 0; l < m_vpListeners.size(); l++) { CListener* pListener = m_vpListeners[l]; 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 (unsigned int m = 0; m < m_vsMotd.size(); m++) { config.AddKeyValuePair("Motd", m_vsMotd[m].FirstLine()); } for (unsigned int v = 0; v < m_vsBindHosts.size(); v++) { config.AddKeyValuePair("BindHost", m_vsBindHosts[v].FirstLine()); } for (unsigned int v = 0; v < m_vsTrustedProxies.size(); v++) { config.AddKeyValuePair("TrustedProxy", m_vsTrustedProxies[v].FirstLine()); } CModules& Mods = GetModules(); for (unsigned int a = 0; a < Mods.size(); a++) { CString sName = Mods[a]->GetModName(); CString sArgs = Mods[a]->GetArgs(); if (!sArgs.empty()) { sArgs = " " + sArgs.FirstLine(); } config.AddKeyValuePair("LoadModule", sName.FirstLine() + sArgs); } for (map::iterator it = m_msUsers.begin(); it != m_msUsers.end(); ++it) { 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 http://en.znc.in/wiki/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 (set::const_iterator it = ssGlobalMods.begin(); it != ssGlobalMods.end(); ++it) { vsGlobalModNames.push_back(it->GetName()); vsLines.push_back("LoadModule = " + it->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, "Got ZNC?"); 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 (set::const_iterator it = ssUserMods.begin(); it != ssUserMods.end(); ++it) { vsUserModNames.push_back(it->GetName()); vsLines.push_back("\tLoadModule = " + it->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 (set::const_iterator it = ssNetworkMods.begin(); it != ssNetworkMods.end(); ++it) { vsNetworkModNames.push_back(it->GetName()); vsLines.push_back("\t\tLoadModule = " + it->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 (unsigned int a = 0; a < vsLines.size(); a++) { if (bFileOpen) { File.Write(vsLines[a] + "\n"); } else { cout << vsLines[a] << 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 (unsigned int a = 0; a < vsLines.size(); a++) { cout << vsLines[a] << 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(); return bFileOpen && CUtils::GetBoolInput("Launch ZNC now?", true); } 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); return DoRehash(sError); } bool CZNC::RehashConfig(CString& sError) { ALLMODULECALL(OnPreRehash(), NOTHING); // This clears m_msDelUsers HandleUserDeletion(); // Mark all users as going-to-be deleted m_msDelUsers = m_msUsers; m_msUsers.clear(); if (DoRehash(sError)) { ALLMODULECALL(OnPostRehash(), NOTHING); return true; } // Rehashing failed, try to recover CString s; while (!m_msDelUsers.empty()) { AddUser(m_msDelUsers.begin()->second, s); m_msDelUsers.erase(m_msDelUsers.begin()); } return false; } bool CZNC::DoRehash(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; CConfig config; if (!config.Parse(File, sError)) { CUtils::PrintStatus(false, sError); return false; } CUtils::PrintStatus(true); CString sSavedVersion; config.FindStringEntry("version", sSavedVersion); 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) { if (sSavedVersion.empty()) { sSavedVersion = "< 0.203"; } 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()); } m_vsBindHosts.clear(); m_vsTrustedProxies.clear(); m_vsMotd.clear(); // Delete all listeners while (!m_vpListeners.empty()) { delete m_vpListeners[0]; m_vpListeners.erase(m_vpListeners.begin()); } MCString msModules; // Modules are queued for later loading VCString vsList; VCString::const_iterator vit; config.FindStringVector("loadmodule", vsList); for (vit = vsList.begin(); vit != vsList.end(); ++vit) { CString sModName = vit->Token(0); CString sArgs = vit->Token(1, true); if (sModName == "saslauth" && tSavedVersion < make_tuple(0, 207)) { // XXX compatibility crap, added in 0.207 CUtils::PrintMessage("saslauth module was renamed to cyrusauth. Loading cyrusauth instead."); sModName = "cyrusauth"; } 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, NULL, NULL, sModRet); CUtils::PrintStatus(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, NULL, NULL, sModRet); CUtils::PrintStatus(bModRet, sModRet); if (!bModRet) { sError = sModRet; return false; } } else CUtils::PrintMessage("Module [" + sModName + "] already loaded."); msModules[sModName] = sArgs; } CString sISpoofFormat, sISpoofFile; config.FindStringEntry("ispoofformat", sISpoofFormat); config.FindStringEntry("ispooffile", sISpoofFile); if (!sISpoofFormat.empty() || !sISpoofFile.empty()) { CModule *pIdentFileMod = GetModules().FindModule("identfile"); if (!pIdentFileMod) { CUtils::PrintAction("Loading global Module [identfile]"); CString sModRet; bool bModRet = GetModules().LoadModule("identfile", "", CModInfo::GlobalModule, NULL, NULL, sModRet); CUtils::PrintStatus(bModRet, sModRet); if (!bModRet) { sError = sModRet; return false; } pIdentFileMod = GetModules().FindModule("identfile"); msModules["identfile"] = ""; } pIdentFileMod->SetNV("File", sISpoofFile); pIdentFileMod->SetNV("Format", sISpoofFormat); } config.FindStringVector("motd", vsList); for (vit = vsList.begin(); vit != vsList.end(); ++vit) { AddMotd(*vit); } config.FindStringVector("bindhost", vsList); for (vit = vsList.begin(); vit != vsList.end(); ++vit) { AddBindHost(*vit); } config.FindStringVector("trustedproxy", vsList); for (vit = vsList.begin(); vit != vsList.end(); ++vit) { AddTrustedProxy(*vit); } config.FindStringVector("vhost", vsList); for (vit = vsList.begin(); vit != vsList.end(); ++vit) { AddBindHost(*vit); } 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("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("sslprotocols", m_sSSLProtocols)) { VCString vsProtocols; m_sSSLProtocols.Split(" ", vsProtocols, false, "", "", true, true); for (CString& sProtocol : vsProtocols) { unsigned int uFlag = 0; bool bEnable = sProtocol.TrimPrefix("+"); bool bDisable = sProtocol.TrimPrefix("-"); 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 { CUtils::PrintError("Invalid SSLProtocols value [" + sProtocol + "]"); CUtils::PrintError("The syntax is [SSLProtocols = [+|-] ...]"); CUtils::PrintError("Available protocols are [SSLv2, SSLv3, TLSv1, TLSv1.1, TLSv1.2]"); return false; } if (bEnable) { m_uDisabledSSLProtocols &= ~uFlag; } else if (bDisable) { m_uDisabledSSLProtocols |= uFlag; } else { m_uDisabledSSLProtocols = ~uFlag; } } } // This has to be after SSLCertFile is handled since it uses that value const char *szListenerEntries[] = { "listen", "listen6", "listen4", "listener", "listener6", "listener4" }; const size_t numListenerEntries = sizeof(szListenerEntries) / sizeof(szListenerEntries[0]); for (size_t i = 0; i < numListenerEntries; i++) { config.FindStringVector(szListenerEntries[i], vsList); vit = vsList.begin(); for (; vit != vsList.end(); ++vit) { if (!AddListener(szListenerEntries[i] + CString(" ") + *vit, sError)) return false; } } CConfig::SubConfig subConf; CConfig::SubConfig::const_iterator subIt; config.FindSubConfig("listener", subConf); for (subIt = subConf.begin(); subIt != subConf.end(); ++subIt) { 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; } } config.FindSubConfig("user", subConf); for (subIt = subConf.begin(); subIt != subConf.end(); ++subIt) { const CString& sUserName = subIt->first; CConfig* pSubConf = subIt->second.m_pSubConfig; CUser* pRealUser = NULL; CUtils::PrintMessage("Loading user [" + sUserName + "]"); // Either create a CUser* or use an existing one map::iterator it = m_msDelUsers.find(sUserName); if (it != m_msDelUsers.end()) { pRealUser = it->second; m_msDelUsers.erase(it); } 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); delete pUser; pUser = NULL; return false; } if (!pSubConf->empty()) { sError = "Unhandled lines in config for User [" + sUserName + "]!"; CUtils::PrintError(sError); DumpConfig(pSubConf); return false; } CString sErr; if (pRealUser) { if (!pRealUser->Clone(*pUser, sErr) || !AddUser(pRealUser, sErr)) { sError = "Invalid user [" + pUser->GetUserName() + "] " + sErr; DEBUG("CUser::Clone() failed in rehash"); } pUser->SetBeingDeleted(true); delete pUser; pUser = NULL; } else if (!AddUser(pUser, sErr)) { sError = "Invalid user [" + pUser->GetUserName() + "] " + sErr; } if (!sError.empty()) { CUtils::PrintError(sError); if (pUser) { pUser->SetBeingDeleted(true); delete pUser; pUser = NULL; } return false; } pUser = NULL; pRealUser = NULL; } if (!config.empty()) { sError = "Unhandled lines in config!"; CUtils::PrintError(sError); DumpConfig(&config); return false; } // Unload modules which are no longer in the config set ssUnload; for (size_t i = 0; i < GetModules().size(); i++) { CModule *pCurMod = GetModules()[i]; if (msModules.find(pCurMod->GetModName()) == msModules.end()) ssUnload.insert(pCurMod->GetModName()); } for (set::iterator it = ssUnload.begin(); it != ssUnload.end(); ++it) { if (GetModules().UnloadModule(*it)) CUtils::PrintMessage("Unloaded global module [" + *it + "]"); else CUtils::PrintMessage("Could not unload [" + *it + "]"); } if (m_msUsers.empty()) { sError = "You must define at least one user in your config."; CUtils::PrintError(sError); return false; } if (m_vpListeners.empty()) { sError = "You must supply at least one Listen port in your config."; CUtils::PrintError(sError); return false; } return true; } 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::ClearBindHosts() { m_vsBindHosts.clear(); } bool CZNC::AddBindHost(const CString& sHost) { if (sHost.empty()) { return false; } for (unsigned int a = 0; a < m_vsBindHosts.size(); a++) { if (m_vsBindHosts[a].Equals(sHost)) { return false; } } m_vsBindHosts.push_back(sHost); return true; } bool CZNC::RemBindHost(const CString& sHost) { VCString::iterator it; for (it = m_vsBindHosts.begin(); it != m_vsBindHosts.end(); ++it) { if (sHost.Equals(*it)) { m_vsBindHosts.erase(it); return true; } } return false; } void CZNC::ClearTrustedProxies() { m_vsTrustedProxies.clear(); } bool CZNC::AddTrustedProxy(const CString& sHost) { if (sHost.empty()) { return false; } for (unsigned int a = 0; a < m_vsTrustedProxies.size(); a++) { if (m_vsTrustedProxies[a].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 (map::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) { if (bAdminOnly && !a->second->IsAdmin()) continue; if (a->second != pSkipUser) { CString sMsg = sMessage; bool bContinue = false; USERMODULECALL(OnBroadcast(sMsg), a->second, NULL, &bContinue); if (bContinue) continue; a->second->PutStatusNotice("*** " + sMsg, NULL, 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) ? NULL : 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::const_iterator it; map musLoaded; map::iterator musIt; map mnsLoaded; map::iterator mnsIt; // Unload the module for every user and network for (it = m_msUsers.begin(); it != m_msUsers.end(); ++it) { 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(); vector::iterator it2; for (it2 = vNetworks.begin(); it2 != vNetworks.end(); ++it2) { CIRCNetwork *pNetwork = *it2; 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, NULL, NULL, sErr)) { DEBUG("Failed to reload [" << sModule << "] globally [" << sErr << "]"); bError = true; } } // Reload the module for all users for (musIt = musLoaded.begin(); musIt != musLoaded.end(); ++musIt) { CUser *pUser = musIt->first; CString& sArgs = musIt->second; if (!pUser->GetModules().LoadModule(sModule, sArgs, CModInfo::UserModule, pUser, NULL, sErr)) { DEBUG("Failed to reload [" << sModule << "] for [" << pUser->GetUserName() << "] [" << sErr << "]"); bError = true; } } // Reload the module for all networks for (mnsIt = mnsLoaded.begin(); mnsIt != mnsLoaded.end(); ++mnsIt) { CIRCNetwork *pNetwork = mnsIt->first; CString& sArgs = mnsIt->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 NULL; } 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) { if (FindUser(pUser->GetUserName()) != NULL) { sErrorRet = "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; 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) { vector::iterator it; for (it = m_vpListeners.begin(); it < m_vpListeners.end(); ++it) { if ((*it)->GetPort() != uPort) continue; if ((*it)->GetBindHost() != sBindHost) continue; if ((*it)->GetAddrType() != eAddr) continue; return *it; } return NULL; } 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.find(" ") != CString::npos) { sBindHost = sValue.Token(0, false, " "); sPort = sValue.Token(1, true, " "); } else { sPort = sValue; } if (sPort.Left(1) == "+") { sPort.LeftChomp(); 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 = "IPV6 is not enabled"; CUtils::PrintStatus(false, sError); return false; } #endif #ifndef HAVE_LIBSSL if (bSSL) { sError = "SSL is not enabled"; CUtils::PrintStatus(false, sError); return false; } #else CString sPemFile = GetPemLocation(); if (bSSL && !CFile::Exists(sPemFile)) { sError = "Unable to locate pem file: [" + 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 = "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 doesnt 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) { vector::iterator it; for (it = m_vpListeners.begin(); it < m_vpListeners.end(); ++it) { if (*it == pListener) { m_vpListeners.erase(it); delete pListener; return true; } } return false; } static CZNC* s_pZNC = NULL; 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 = NULL; } 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 (map::const_iterator it = msUsers.begin(); it != msUsers.end(); ++it) { ret[it->first] = TrafficStatsPair(it->second->BytesRead(), it->second->BytesWritten()); uiUsers_in += it->second->BytesRead(); uiUsers_out += it->second->BytesWritten(); } for (CSockManager::const_iterator it = m_Manager.begin(); it != m_Manager.end(); ++it) { CUser *pUser = NULL; if ((*it)->GetSockName().Left(5) == "IRC::") { pUser = ((CIRCSock *) *it)->GetNetwork()->GetUser(); } else if ((*it)->GetSockName().Left(5) == "USR::") { pUser = ((CClient*) *it)->GetUser(); } if (pUser) { ret[pUser->GetUserName()].first += (*it)->GetBytesRead(); ret[pUser->GetUserName()].second += (*it)->GetBytesWritten(); uiUsers_in += (*it)->GetBytesRead(); uiUsers_out += (*it)->GetBytesWritten(); } else { uiZNC_in += (*it)->GetBytesRead(); uiZNC_out += (*it)->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; } 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; } virtual ~CConnectQueueTimer() { // This is only needed when ZNC shuts down: // CZNC::~CZNC() sets its CConnectQueueTimer pointer to NULL 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: virtual void RunJob() { 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 != NULL) { m_pConnectQueueTimer->Start(i); } m_uiConnectDelay = i; } 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 = NULL; } } 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::AddNetworkToQueue(CIRCNetwork *pNetwork) { // Make sure we are not already in the queue for (list::const_iterator it = m_lpConnectQueue.begin(); it != m_lpConnectQueue.end(); ++it) { if (*it == pNetwork) { return; } } m_lpConnectQueue.push_back(pNetwork); EnableConnectQueue(); } void CZNC::LeakConnectQueueTimer(CConnectQueueTimer *pTimer) { if (m_pConnectQueueTimer == pTimer) m_pConnectQueueTimer = NULL; } bool CZNC::WaitForChildLock() { return m_pLockFile && m_pLockFile->ExLock(); }