<commit_before>/*
 * Copyright (C) 2004-2011  See the AUTHORS file for details.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 */

#include "znc.h"
#include "Chan.h"
#include "FileUtils.h"
#include "IRCSock.h"
#include "Server.h"
#include "User.h"
#include "Listener.h"
#include "Config.h"
#include <list>

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 CGlobalModules();
	m_uiConnectDelay = 5;
	m_uiAnonIPLimit = 10;
	m_uBytesRead = 0;
	m_uBytesWritten = 0;
	m_uiMaxBufferSize = 500;
	m_pConnectUserTimer = NULL;
	m_eConfigState = ECONFIG_NOTHING;
	m_TimeStarted = time(NULL);
	m_sConnectThrottle.SetTTL(30000);
	m_pLockFile = NULL;
	m_bProtectWebSessions = true;
}

CZNC::~CZNC() {
	m_pModules->UnloadAll();

	for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
		a->second->GetModules().UnloadAll();
	}

	for (size_t b = 0; b < m_vpListeners.size(); b++) {
		delete m_vpListeners[b];
	}

	for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
		a->second->SetBeingDeleted(true);
	}

	m_pConnectUserTimer = NULL;
	// This deletes m_pConnectUserTimer
	m_Manager.Cleanup();
	DeleteUsers();

	delete m_pModules;
	delete m_pLockFile;

	ShutdownCsocket();
	DeletePidFile();
}

CString CZNC::GetVersion() {
	char szBuf[128];

	snprintf(szBuf, sizeof(szBuf), "%1.3f"VERSION_EXTRA, VERSION);
	// If snprintf overflows (which I doubt), we want to be on the safe side
	szBuf[sizeof(szBuf) - 1] = '\0';

	return szBuf;
}

CString CZNC::GetTag(bool bIncludeVersion) {
	if (!bIncludeVersion) {
		return "ZNC - http://znc.in";
	}

	char szBuf[128];
	snprintf(szBuf, sizeof(szBuf), "ZNC %1.3f"VERSION_EXTRA" - http://znc.in", VERSION);
	// If snprintf overflows (which I doubt), we want to be on the safe side
	szBuf[sizeof(szBuf) - 1] = '\0';

	return szBuf;
}

CString CZNC::GetUptime() const {
	time_t now = time(NULL);
	return CString::ToTimeStr(now - TimeStarted());
}

bool CZNC::OnBoot() {
	ALLMODULECALL(OnBoot(), return false);

	return true;
}

bool CZNC::ConnectUser(CUser *pUser) {
	CString sSockName = "IRC::" + pUser->GetUserName();
	CIRCSock* pIRCSock = pUser->GetIRCSock();

	if (!pUser->GetIRCConnectEnabled())
		return false;

	if (pIRCSock || !pUser->HasServers())
		return false;

	CServer* pServer = pUser->GetNextServer();

	if (!pServer)
		return false;

	if (m_sConnectThrottle.GetItem(pServer->GetName()))
		return false;

	m_sConnectThrottle.AddItem(pServer->GetName());

	DEBUG("User [" << pUser->GetUserName() << "] is connecting to [" << pServer->GetString(false) << "] ...");
	pUser->PutStatus("Attempting to connect to [" + pServer->GetString(false) + "] ...");

	pIRCSock = new CIRCSock(pUser);
	pIRCSock->SetPass(pServer->GetPass());

	bool bSSL = false;
#ifdef HAVE_LIBSSL
	if (pServer->IsSSL()) {
		bSSL = true;
	}
#endif

	MODULECALL(OnIRCConnecting(pIRCSock), pUser, NULL,
		DEBUG("Some module aborted the connection attempt");
		pUser->PutStatus("Some module aborted the connection attempt");
		delete pIRCSock;
		return false;
	);

	if (!m_Manager.Connect(pServer->GetName(), pServer->GetPort(), sSockName, 120, bSSL, pUser->GetBindHost(), pIRCSock)) {
		pUser->PutStatus("Unable to connect. (Bad host?)");
	}

	return true;
}

bool CZNC::HandleUserDeletion()
{
	map<CString, CUser*>::iterator it;
	map<CString, CUser*>::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());

		CIRCSock* pIRCSock = pUser->GetIRCSock();

		if (pIRCSock) {
			m_Manager.DelSockByAddr(pIRCSock);
		}

		pUser->DelClients();
		pUser->DelModules();
		CWebSock::FinishUserSessions(*pUser);
		AddBytesRead(pUser->BytesRead());
		AddBytesWritten(pUser->BytesWritten());
		delete pUser;
	}

	m_msDelUsers.clear();

	return true;
}

void CZNC::Loop() {
	while (true) {
		CString sError;

		switch (GetConfigState()) {
		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:
			SetConfigState(ECONFIG_NOTHING);

			if (WriteConfig()) {
				Broadcast("Writing the config succeeded", true);
			} else {
				Broadcast("Writing the config file failed", 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
		// 500 msec to 600 sec
		m_Manager.DynamicSelectLoop(500 * 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();
	const char* pHostName = getenv("HOSTNAME");
	CString sHost;

	if (pHostName) {
		sHost = pHostName;
	}

	if (CFile::Exists(sPemFile)) {
		CUtils::PrintError("Pem file [" + sPemFile + "] already exists");
		return false;
	}

	while (!CUtils::GetInput("hostname of your shell", sHost, sHost, "including the '.com' portion")) ;

	CUtils::PrintAction("Writing Pem file [" + sPemFile + "]");
	FILE *f = fopen(sPemFile.c_str(), "w");

	if (!f) {
		CUtils::PrintStatus(false, "Unable to open");
		return false;
	}

	CUtils::GenerateCert(f, sHost);
	fclose(f);

	CUtils::PrintStatus(true);
	return true;
#endif
}

void CZNC::DeleteUsers() {
	for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
		a->second->SetBeingDeleted(true);
		delete a->second;
	}

	m_msUsers.clear();
	DisableConnectUser();
}

bool CZNC::IsHostAllowed(const CString& sHostMask) const {
	for (map<CString,CUser*>::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";

	if (!CFile::Exists(sModPath)) {
		CDir::MakeDir(sModPath);
	}

	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()) {
		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)) {
		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()) {
		pFile->Delete();
		delete pFile;
		return false;
	}

	pFile->Write(MakeConfigHeader() + "\n");

	pFile->Write("AnonIPLimit  = " + CString(m_uiAnonIPLimit) + "\n");
	pFile->Write("MaxBufferSize= " + CString(m_uiMaxBufferSize) + "\n");
	pFile->Write("SSLCertFile  = " + CString(m_sSSLCertFile) + "\n");
	pFile->Write("ProtectWebSessions = " + CString(m_bProtectWebSessions) + "\n");

	for (size_t l = 0; l < m_vpListeners.size(); l++) {
		CListener* pListener = m_vpListeners[l];
		CString sHostPortion = pListener->GetBindHost();

		if (!sHostPortion.empty()) {
			sHostPortion = sHostPortion.FirstLine() + " ";
		}

		CString sAcceptProtocol;
		if(pListener->GetAcceptType() == CListener::ACCEPT_IRC)
			sAcceptProtocol = "irc_only ";
		else if(pListener->GetAcceptType() == CListener::ACCEPT_HTTP)
			sAcceptProtocol = "web_only ";

		CString s6;
		switch (pListener->GetAddrType()) {
			case ADDR_IPV4ONLY:
				s6 = "4";
				break;
			case ADDR_IPV6ONLY:
				s6 = "6";
				break;
			case ADDR_ALL:
				s6 = " ";
				break;
		}

		pFile->Write("Listener" + s6 + "    = " + sAcceptProtocol + sHostPortion +
			CString((pListener->IsSSL()) ? "+" : "") + CString(pListener->GetPort()) + "\n");
	}

	pFile->Write("ConnectDelay = " + CString(m_uiConnectDelay) + "\n");
	pFile->Write("ServerThrottle = " + CString(m_sConnectThrottle.GetTTL()/1000) + "\n");

	if (!m_sPidFile.empty()) {
		pFile->Write("PidFile      = " + m_sPidFile.FirstLine() + "\n");
	}

	if (!m_sSkinName.empty()) {
		pFile->Write("Skin         = " + m_sSkinName.FirstLine() + "\n");
	}

	if (!m_sStatusPrefix.empty()) {
		pFile->Write("StatusPrefix = " + m_sStatusPrefix.FirstLine() + "\n");
	}

	for (unsigned int m = 0; m < m_vsMotd.size(); m++) {
		pFile->Write("Motd         = " + m_vsMotd[m].FirstLine() + "\n");
	}

	for (unsigned int v = 0; v < m_vsBindHosts.size(); v++) {
		pFile->Write("BindHost     = " + m_vsBindHosts[v].FirstLine() + "\n");
	}

	CGlobalModules& 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();
		}

		pFile->Write("LoadModule   = " + sName.FirstLine() + sArgs + "\n");
	}

	for (map<CString,CUser*>::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;
		}

		pFile->Write("\n");

		if (!it->second->WriteConfig(*pFile)) {
			DEBUG("** Error writing config for user [" << it->first << "]");
		}
	}

	// 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");
		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 *admin instead.\n"
		"//\n"
		"// Buf 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;
	VCString vsLines;

	vsLines.push_back(MakeConfigHeader());

	m_sConfigFile = ExpandConfigPath(sConfigFile);
	CUtils::PrintMessage("Building new config");

	CUtils::PrintMessage("");
	CUtils::PrintMessage("First let's start with some global settings...");
	CUtils::PrintMessage("");

	// Listen
	CString s6;
	CString sListenHost;
	CString sSSL;
	unsigned int uListenPort = 0;
	bool bSuccess;

	do {
		bSuccess = true;
		while (!CUtils::GetNumInput("What port would you like ZNC to listen on?", uListenPort, 1, 65535)) ;

#ifdef HAVE_LIBSSL
		if (CUtils::GetBoolInput("Would you like ZNC to listen using SSL?", !sSSL.empty())) {
			sSSL = "+";

			CString sPemFile = GetPemLocation();
			if (!CFile::Exists(sPemFile)) {
				CUtils::PrintError("Unable to locate pem file: [" + sPemFile + "]");
				if (CUtils::GetBoolInput("Would you like to create a new pem file now?",
							true)) {
					WritePemFile();
				}
			}
		} else
			sSSL = "";
#endif

#ifdef HAVE_IPV6
		if (CUtils::GetBoolInput("Would you like ZNC to listen using ipv6?", s6 == " ")) {
			s6 = " ";
		} else {
			s6 = "4";
		}
#endif

		CUtils::GetInput("Listen Host", sListenHost, sListenHost, "Blank for all ips");

		CUtils::PrintAction("Verifying the listener");
		CListener* pListener = new CListener(uListenPort, sListenHost, !sSSL.empty(),
				s6.empty() ? ADDR_IPV4ONLY : ADDR_ALL, CListener::ACCEPT_ALL);
		if (!pListener->Listen()) {
			CUtils::PrintStatus(false, FormatBindError());
			bSuccess = false;
		} else
			CUtils::PrintStatus(true);
		delete pListener;
	} while (!bSuccess);

	if (!sListenHost.empty()) {
		sListenHost += " ";
	}

	vsLines.push_back("Listener" + s6 + "  = " + sListenHost + sSSL + CString(uListenPort));
	// !Listen

	set<CModInfo> ssGlobalMods;
	GetModules().GetAvailableMods(ssGlobalMods, true);
	size_t uNrOtherGlobalMods = FilterUncommonModules(ssGlobalMods);

	if (!ssGlobalMods.empty()) {
		CUtils::PrintMessage("");
		CUtils::PrintMessage("-- Global Modules --");
		CUtils::PrintMessage("");

		CTable Table;
		Table.AddColumn("Name");
		Table.AddColumn("Description");
		set<CModInfo>::iterator it;
<commit_msg>Fix verifying listener in --makeconf.<commit_after>/*
 * Copyright (C) 2004-2011  See the AUTHORS file for details.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation.
 */

#include "znc.h"
#include "Chan.h"
#include "FileUtils.h"
#include "IRCSock.h"
#include "Server.h"
#include "User.h"
#include "Listener.h"
#include "Config.h"
#include <list>

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 CGlobalModules();
	m_uiConnectDelay = 5;
	m_uiAnonIPLimit = 10;
	m_uBytesRead = 0;
	m_uBytesWritten = 0;
	m_uiMaxBufferSize = 500;
	m_pConnectUserTimer = NULL;
	m_eConfigState = ECONFIG_NOTHING;
	m_TimeStarted = time(NULL);
	m_sConnectThrottle.SetTTL(30000);
	m_pLockFile = NULL;
	m_bProtectWebSessions = true;
}

CZNC::~CZNC() {
	m_pModules->UnloadAll();

	for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
		a->second->GetModules().UnloadAll();
	}

	for (size_t b = 0; b < m_vpListeners.size(); b++) {
		delete m_vpListeners[b];
	}

	for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
		a->second->SetBeingDeleted(true);
	}

	m_pConnectUserTimer = NULL;
	// This deletes m_pConnectUserTimer
	m_Manager.Cleanup();
	DeleteUsers();

	delete m_pModules;
	delete m_pLockFile;

	ShutdownCsocket();
	DeletePidFile();
}

CString CZNC::GetVersion() {
	char szBuf[128];

	snprintf(szBuf, sizeof(szBuf), "%1.3f"VERSION_EXTRA, VERSION);
	// If snprintf overflows (which I doubt), we want to be on the safe side
	szBuf[sizeof(szBuf) - 1] = '\0';

	return szBuf;
}

CString CZNC::GetTag(bool bIncludeVersion) {
	if (!bIncludeVersion) {
		return "ZNC - http://znc.in";
	}

	char szBuf[128];
	snprintf(szBuf, sizeof(szBuf), "ZNC %1.3f"VERSION_EXTRA" - http://znc.in", VERSION);
	// If snprintf overflows (which I doubt), we want to be on the safe side
	szBuf[sizeof(szBuf) - 1] = '\0';

	return szBuf;
}

CString CZNC::GetUptime() const {
	time_t now = time(NULL);
	return CString::ToTimeStr(now - TimeStarted());
}

bool CZNC::OnBoot() {
	ALLMODULECALL(OnBoot(), return false);

	return true;
}

bool CZNC::ConnectUser(CUser *pUser) {
	CString sSockName = "IRC::" + pUser->GetUserName();
	CIRCSock* pIRCSock = pUser->GetIRCSock();

	if (!pUser->GetIRCConnectEnabled())
		return false;

	if (pIRCSock || !pUser->HasServers())
		return false;

	CServer* pServer = pUser->GetNextServer();

	if (!pServer)
		return false;

	if (m_sConnectThrottle.GetItem(pServer->GetName()))
		return false;

	m_sConnectThrottle.AddItem(pServer->GetName());

	DEBUG("User [" << pUser->GetUserName() << "] is connecting to [" << pServer->GetString(false) << "] ...");
	pUser->PutStatus("Attempting to connect to [" + pServer->GetString(false) + "] ...");

	pIRCSock = new CIRCSock(pUser);
	pIRCSock->SetPass(pServer->GetPass());

	bool bSSL = false;
#ifdef HAVE_LIBSSL
	if (pServer->IsSSL()) {
		bSSL = true;
	}
#endif

	MODULECALL(OnIRCConnecting(pIRCSock), pUser, NULL,
		DEBUG("Some module aborted the connection attempt");
		pUser->PutStatus("Some module aborted the connection attempt");
		delete pIRCSock;
		return false;
	);

	if (!m_Manager.Connect(pServer->GetName(), pServer->GetPort(), sSockName, 120, bSSL, pUser->GetBindHost(), pIRCSock)) {
		pUser->PutStatus("Unable to connect. (Bad host?)");
	}

	return true;
}

bool CZNC::HandleUserDeletion()
{
	map<CString, CUser*>::iterator it;
	map<CString, CUser*>::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());

		CIRCSock* pIRCSock = pUser->GetIRCSock();

		if (pIRCSock) {
			m_Manager.DelSockByAddr(pIRCSock);
		}

		pUser->DelClients();
		pUser->DelModules();
		CWebSock::FinishUserSessions(*pUser);
		AddBytesRead(pUser->BytesRead());
		AddBytesWritten(pUser->BytesWritten());
		delete pUser;
	}

	m_msDelUsers.clear();

	return true;
}

void CZNC::Loop() {
	while (true) {
		CString sError;

		switch (GetConfigState()) {
		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:
			SetConfigState(ECONFIG_NOTHING);

			if (WriteConfig()) {
				Broadcast("Writing the config succeeded", true);
			} else {
				Broadcast("Writing the config file failed", 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
		// 500 msec to 600 sec
		m_Manager.DynamicSelectLoop(500 * 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();
	const char* pHostName = getenv("HOSTNAME");
	CString sHost;

	if (pHostName) {
		sHost = pHostName;
	}

	if (CFile::Exists(sPemFile)) {
		CUtils::PrintError("Pem file [" + sPemFile + "] already exists");
		return false;
	}

	while (!CUtils::GetInput("hostname of your shell", sHost, sHost, "including the '.com' portion")) ;

	CUtils::PrintAction("Writing Pem file [" + sPemFile + "]");
	FILE *f = fopen(sPemFile.c_str(), "w");

	if (!f) {
		CUtils::PrintStatus(false, "Unable to open");
		return false;
	}

	CUtils::GenerateCert(f, sHost);
	fclose(f);

	CUtils::PrintStatus(true);
	return true;
#endif
}

void CZNC::DeleteUsers() {
	for (map<CString,CUser*>::iterator a = m_msUsers.begin(); a != m_msUsers.end(); ++a) {
		a->second->SetBeingDeleted(true);
		delete a->second;
	}

	m_msUsers.clear();
	DisableConnectUser();
}

bool CZNC::IsHostAllowed(const CString& sHostMask) const {
	for (map<CString,CUser*>::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";

	if (!CFile::Exists(sModPath)) {
		CDir::MakeDir(sModPath);
	}

	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()) {
		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)) {
		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()) {
		pFile->Delete();
		delete pFile;
		return false;
	}

	pFile->Write(MakeConfigHeader() + "\n");

	pFile->Write("AnonIPLimit  = " + CString(m_uiAnonIPLimit) + "\n");
	pFile->Write("MaxBufferSize= " + CString(m_uiMaxBufferSize) + "\n");
	pFile->Write("SSLCertFile  = " + CString(m_sSSLCertFile) + "\n");
	pFile->Write("ProtectWebSessions = " + CString(m_bProtectWebSessions) + "\n");

	for (size_t l = 0; l < m_vpListeners.size(); l++) {
		CListener* pListener = m_vpListeners[l];
		CString sHostPortion = pListener->GetBindHost();

		if (!sHostPortion.empty()) {
			sHostPortion = sHostPortion.FirstLine() + " ";
		}

		CString sAcceptProtocol;
		if(pListener->GetAcceptType() == CListener::ACCEPT_IRC)
			sAcceptProtocol = "irc_only ";
		else if(pListener->GetAcceptType() == CListener::ACCEPT_HTTP)
			sAcceptProtocol = "web_only ";

		CString s6;
		switch (pListener->GetAddrType()) {
			case ADDR_IPV4ONLY:
				s6 = "4";
				break;
			case ADDR_IPV6ONLY:
				s6 = "6";
				break;
			case ADDR_ALL:
				s6 = " ";
				break;
		}

		pFile->Write("Listener" + s6 + "    = " + sAcceptProtocol + sHostPortion +
			CString((pListener->IsSSL()) ? "+" : "") + CString(pListener->GetPort()) + "\n");
	}

	pFile->Write("ConnectDelay = " + CString(m_uiConnectDelay) + "\n");
	pFile->Write("ServerThrottle = " + CString(m_sConnectThrottle.GetTTL()/1000) + "\n");

	if (!m_sPidFile.empty()) {
		pFile->Write("PidFile      = " + m_sPidFile.FirstLine() + "\n");
	}

	if (!m_sSkinName.empty()) {
		pFile->Write("Skin         = " + m_sSkinName.FirstLine() + "\n");
	}

	if (!m_sStatusPrefix.empty()) {
		pFile->Write("StatusPrefix = " + m_sStatusPrefix.FirstLine() + "\n");
	}

	for (unsigned int m = 0; m < m_vsMotd.size(); m++) {
		pFile->Write("Motd         = " + m_vsMotd[m].FirstLine() + "\n");
	}

	for (unsigned int v = 0; v < m_vsBindHosts.size(); v++) {
		pFile->Write("BindHost     = " + m_vsBindHosts[v].FirstLine() + "\n");
	}

	CGlobalModules& 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();
		}

		pFile->Write("LoadModule   = " + sName.FirstLine() + sArgs + "\n");
	}

	for (map<CString,CUser*>::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;
		}

		pFile->Write("\n");

		if (!it->second->WriteConfig(*pFile)) {
			DEBUG("** Error writing config for user [" << it->first << "]");
		}
	}

	// 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");
		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 *admin instead.\n"
		"//\n"
		"// Buf 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;
	VCString vsLines;

	vsLines.push_back(MakeConfigHeader());

	m_sConfigFile = ExpandConfigPath(sConfigFile);
	CUtils::PrintMessage("Building new config");

	CUtils::PrintMessage("");
	CUtils::PrintMessage("First let's start with some global settings...");
	CUtils::PrintMessage("");

	// Listen
#ifdef HAVE_IPV6
	bool b6 = true;
#else
	bool b6 = false;
#endif
	CString sListenHost;
	CString sSSL;
	unsigned int uListenPort = 0;
	bool bSuccess;

	do {
		bSuccess = true;
		while (!CUtils::GetNumInput("What port would you like ZNC to listen on?", uListenPort, 1, 65535)) ;

#ifdef HAVE_LIBSSL
		if (CUtils::GetBoolInput("Would you like ZNC to listen using SSL?", !sSSL.empty())) {
			sSSL = "+";

			CString sPemFile = GetPemLocation();
			if (!CFile::Exists(sPemFile)) {
				CUtils::PrintError("Unable to locate pem file: [" + sPemFile + "]");
				if (CUtils::GetBoolInput("Would you like to create a new pem file now?",
							true)) {
					WritePemFile();
				}
			}
		} else
			sSSL = "";
#endif

#ifdef HAVE_IPV6
		b6 = CUtils::GetBoolInput("Would you like ZNC to listen using ipv6?", b6);
#endif

		CUtils::GetInput("Listen Host", sListenHost, sListenHost, "Blank for all ips");

		CUtils::PrintAction("Verifying the listener");
		CListener* pListener = new CListener(uListenPort, sListenHost, !sSSL.empty(),
				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);

	if (!sListenHost.empty()) {
		sListenHost += " ";
	}

	vsLines.push_back("Listener" + CString(b6 ? " " : "4") + "  = " + sListenHost + sSSL + CString(uListenPort));
	// !Listen

	set<CModInfo> ssGlobalMods;
	GetModules().GetAvailableMods(ssGlobalMods, true);
	size_t uNrOtherGlobalMods = FilterUncommonModules(ssGlobalMods);

	if (!ssGlobalMods.empty()) {
		CUtils::PrintMessage("");
		CUtils::PrintMessage("-- Global Modules --");
		CUtils::PrintMessage("");

		CTable Table;
		Table.AddColumn("Name");
		Table.AddColumn("Description");
		set<CModInfo>::iterator it;

{'fraction_non_alphanumeric': 0.16466413496856128, 'fraction_numerical': 0.006816908423084106, 'mean_word_length': 4.21965881397238, 'pattern_counts': {'":': 0, '<': 75, '<?xml version=': 0, '>': 189, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1124808', 'n_tokens_mistral': 13056, 'n_tokens_neox': 12125, 'n_words': 2693}