text: /*
   * 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 <znc/User.h>
  #include <znc/Config.h>
  #include <znc/FileUtils.h>
  #include <znc/IRCNetwork.h>
  #include <znc/IRCSock.h>
  #include <znc/Chan.h>
  #include <math.h>
  #include <algorithm>
  
  using std::vector;
  using std::set;
  
  class CUserTimer : public CCron {
  public:
  	CUserTimer(CUser* pUser) : CCron(), m_pUser(pUser) {
  		SetName("CUserTimer::" + m_pUser->GetUserName());
  		Start(CIRCNetwork::PING_SLACK);
  	}
  	virtual ~CUserTimer() {}
  
  	CUserTimer(const CUserTimer&) = delete;
  	CUserTimer& operator=(const CUserTimer&) = delete;
  
  private:
  protected:
  	void RunJob() override {
  		const vector<CClient*>& vUserClients = m_pUser->GetUserClients();
  
  		for (CClient* pUserClient : vUserClients) {
  			if (pUserClient->GetTimeSinceLastDataTransaction() >= CIRCNetwork::PING_FREQUENCY) {
  				pUserClient->PutClient("PING :ZNC");
  			}
  		}
  	}
  
  	CUser* m_pUser;
  };
  
  CUser::CUser(const CString& sUserName)
  		: m_sUserName(sUserName),
  		  m_sCleanUserName(MakeCleanUserName(sUserName)),
  		  m_sNick(m_sCleanUserName),
  		  m_sAltNick(""),
  		  m_sIdent(m_sCleanUserName),
  		  m_sRealName(sUserName),
  		  m_sBindHost(""),
  		  m_sDCCBindHost(""),
  		  m_sPass(""),
  		  m_sPassSalt(""),
  		  m_sStatusPrefix("*"),
  		  m_sDefaultChanModes(""),
  		  m_sClientEncoding(""),
  		  m_sQuitMsg(""),
  		  m_mssCTCPReplies(),
  		  m_sTimestampFormat("[%H:%M:%S]"),
  		  m_sTimezone(""),
  		  m_eHashType(HASH_NONE),
  		  m_sUserPath(CZNC::Get().GetUserPath() + "/" + sUserName),
  		  m_bMultiClients(true),
  		  m_bDenyLoadMod(false),
  		  m_bAdmin(false),
  		  m_bDenySetBindHost(false),
  		  m_bAutoClearChanBuffer(true),
  		  m_bAutoClearQueryBuffer(true),
  		  m_bBeingDeleted(false),
  		  m_bAppendTimestamp(false),
  		  m_bPrependTimestamp(true),
  		  m_pUserTimer(nullptr),
  		  m_vIRCNetworks(),
  		  m_vClients(),
  		  m_ssAllowedHosts(),
  		  m_uBufferCount(50),
  		  m_uBytesRead(0),
  		  m_uBytesWritten(0),
  		  m_uMaxJoinTries(10),
  		  m_uMaxNetworks(1),
  		  m_uMaxQueryBuffers(50),
  		  m_uMaxJoins(0),
  		  m_sSkinName(""),
  		  m_pModules(new CModules)
  {
  	m_pUserTimer = new CUserTimer(this);
  	CZNC::Get().GetManager().AddCron(m_pUserTimer);
  }
  
  CUser::~CUser() {
  	// Delete networks
  	while (!m_vIRCNetworks.empty()) {
  		delete *m_vIRCNetworks.begin();
  	}
  
  	// Delete clients
  	while (!m_vClients.empty()) {
  		CZNC::Get().GetManager().DelSockByAddr(m_vClients[0]);
  	}
  	m_vClients.clear();
  
  	// Delete modules (unloads all modules!)
  	delete m_pModules;
  	m_pModules = nullptr;
  
  	CZNC::Get().GetManager().DelCronByAddr(m_pUserTimer);
  
  	CZNC::Get().AddBytesRead(BytesRead());
  	CZNC::Get().AddBytesWritten(BytesWritten());
  }
  
  template<class T>
  struct TOption {
  	const char *name;
  	void (CUser::*pSetter)(T);
  };
  
  bool CUser::ParseConfig(CConfig* pConfig, CString& sError) {
  	TOption<const CString&> StringOptions[] = {
  		{ "nick", &CUser::SetNick },
  		{ "quitmsg", &CUser::SetQuitMsg },
  		{ "altnick", &CUser::SetAltNick },
  		{ "ident", &CUser::SetIdent },
  		{ "realname", &CUser::SetRealName },
  		{ "chanmodes", &CUser::SetDefaultChanModes },
  		{ "bindhost", &CUser::SetBindHost },
  		{ "vhost", &CUser::SetBindHost },
  		{ "dccbindhost", &CUser::SetDCCBindHost },
  		{ "dccvhost", &CUser::SetDCCBindHost },
  		{ "timestampformat", &CUser::SetTimestampFormat },
  		{ "skin", &CUser::SetSkinName },
  		{ "clientencoding", &CUser::SetClientEncoding },
  	};
  	TOption<unsigned int> UIntOptions[] = {
  		{ "jointries", &CUser::SetJoinTries },
  		{ "maxnetworks", &CUser::SetMaxNetworks },
  		{ "maxquerybuffers", &CUser::SetMaxQueryBuffers },
  		{ "maxjoins", &CUser::SetMaxJoins },
  	};
  	TOption<bool> BoolOptions[] = {
  		{ "keepbuffer", &CUser::SetKeepBuffer }, // XXX compatibility crap from pre-0.207
  		{ "autoclearchanbuffer", &CUser::SetAutoClearChanBuffer },
  		{ "autoclearquerybuffer", &CUser::SetAutoClearQueryBuffer },
  		{ "multiclients", &CUser::SetMultiClients },
  		{ "denyloadmod", &CUser::SetDenyLoadMod },
  		{ "admin", &CUser::SetAdmin },
  		{ "denysetbindhost", &CUser::SetDenySetBindHost },
  		{ "denysetvhost", &CUser::SetDenySetBindHost },
  		{ "appendtimestamp", &CUser::SetTimestampAppend },
  		{ "prependtimestamp", &CUser::SetTimestampPrepend },
  	};
  
  	for (const auto& Option : StringOptions) {
  		CString sValue;
  		if (pConfig->FindStringEntry(Option.name, sValue))
  			(this->*Option.pSetter)(sValue);
  	}
  	for (const auto& Option : UIntOptions) {
  		CString sValue;
  		if (pConfig->FindStringEntry(Option.name, sValue))
  			(this->*Option.pSetter)(sValue.ToUInt());
  	}
  	for (const auto& Option : BoolOptions) {
  		CString sValue;
  		if (pConfig->FindStringEntry(Option.name, sValue))
  			(this->*Option.pSetter)(sValue.ToBool());
  	}
  
  	VCString vsList;
  	pConfig->FindStringVector("allow", vsList);
  	for (const CString& sHost : vsList) {
  		AddAllowedHost(sHost);
  	}
  	pConfig->FindStringVector("ctcpreply", vsList);
  	for (const CString& sReply : vsList) {
  		AddCTCPReply(sReply.Token(0), sReply.Token(1, true));
  	}
  
  	CString sValue;
  
  	CString sDCCLookupValue;
  	pConfig->FindStringEntry("dcclookupmethod", sDCCLookupValue);
  	if (pConfig->FindStringEntry("bouncedccs", sValue))  {
  		if (sValue.ToBool()) {
  			CUtils::PrintAction("Loading Module [bouncedcc]");
  			CString sModRet;
  			bool bModRet = GetModules().LoadModule("bouncedcc", "", CModInfo::UserModule, this, nullptr, sModRet);
  
  			CUtils::PrintStatus(bModRet, sModRet);
  			if (!bModRet) {
  				sError = sModRet;
  				return false;
  			}
  
  			if (sDCCLookupValue.Equals("Client")) {
  				GetModules().FindModule("bouncedcc")->SetNV("UseClientIP", "1");
  			}
  		}
  	}
  	if (pConfig->FindStringEntry("buffer", sValue))
  		SetBufferCount(sValue.ToUInt(), true);
  	if (pConfig->FindStringEntry("awaysuffix", sValue)) {
  		CUtils::PrintMessage("WARNING: AwaySuffix has been deprecated, instead try -> LoadModule = awaynick %nick%_" + sValue);
  	}
  	if (pConfig->FindStringEntry("autocycle", sValue)) {
  		if (sValue.Equals("true"))
  			CUtils::PrintError("WARNING: AutoCycle has been removed, instead try -> LoadModule = autocycle");
  	}
  	if (pConfig->FindStringEntry("keepnick", sValue)) {
  		if (sValue.Equals("true"))
  			CUtils::PrintError("WARNING: KeepNick has been deprecated, instead try -> LoadModule = keepnick");
  	}
  	if (pConfig->FindStringEntry("statusprefix", sValue)) {
  		if (!SetStatusPrefix(sValue)) {
  			sError = "Invalid StatusPrefix [" + sValue + "] Must be 1-5 chars, no spaces.";
  			CUtils::PrintError(sError);
  			return false;
  		}
  	}
  	if (pConfig->FindStringEntry("timezone", sValue)) {
  		SetTimezone(sValue);
  	}
  	if (pConfig->FindStringEntry("timezoneoffset", sValue)) {
  		if (fabs(sValue.ToDouble()) > 0.1) {
  			CUtils::PrintError("WARNING: TimezoneOffset has been deprecated, now you can set your timezone by name");
  		}
  	}
  	if (pConfig->FindStringEntry("timestamp", sValue)) {
  		if (!sValue.Trim_n().Equals("true")) {
  			if (sValue.Trim_n().Equals("append")) {
  				SetTimestampAppend(true);
  				SetTimestampPrepend(false);
  			} else if (sValue.Trim_n().Equals("prepend")) {
  				SetTimestampAppend(false);
  				SetTimestampPrepend(true);
  			} else if (sValue.Trim_n().Equals("false")) {
  				SetTimestampAppend(false);
  				SetTimestampPrepend(false);
  			} else {
  				SetTimestampFormat(sValue);
  			}
  		}
  	}
  	pConfig->FindStringEntry("pass", sValue);
  	// There are different formats for this available:
  	// Pass = <plain text>
  	// Pass = <md5 hash> -
  	// Pass = plain#<plain text>
  	// Pass = <hash name>#<hash>
  	// Pass = <hash name>#<salted hash>#<salt>#
  	// 'Salted hash' means hash of 'password' + 'salt'
  	// Possible hashes are md5 and sha256
  	if (sValue.Right(1) == "-") {
  		sValue.RightChomp();
  		sValue.Trim();
  		SetPass(sValue, CUser::HASH_MD5);
  	} else {
  		CString sMethod = sValue.Token(0, false, "#");
  		CString sPass = sValue.Token(1, true, "#");
  		if (sMethod == "md5" || sMethod == "sha256") {
  			CUser::eHashType type = CUser::HASH_MD5;
  			if (sMethod == "sha256")
  				type = CUser::HASH_SHA256;
  
  			CString sSalt = sPass.Token(1, false, "#");
  			sPass = sPass.Token(0, false, "#");
  			SetPass(sPass, type, sSalt);
  		} else if (sMethod == "plain") {
  			SetPass(sPass, CUser::HASH_NONE);
  		} else {
  			SetPass(sValue, CUser::HASH_NONE);
  		}
  	}
  	CConfig::SubConfig subConf;
  	CConfig::SubConfig::const_iterator subIt;
  	pConfig->FindSubConfig("pass", subConf);
  	if (!sValue.empty() && !subConf.empty()) {
  		sError = "Password defined more than once";
  		CUtils::PrintError(sError);
  		return false;
  	}
  	subIt = subConf.begin();
  	if (subIt != subConf.end()) {
  		CConfig* pSubConf = subIt->second.m_pSubConfig;
  		CString sHash;
  		CString sMethod;
  		CString sSalt;
  		CUser::eHashType method;
  		pSubConf->FindStringEntry("hash", sHash);
  		pSubConf->FindStringEntry("method", sMethod);
  		pSubConf->FindStringEntry("salt", sSalt);
  		if (sMethod.empty() || sMethod.Equals("plain"))
  			method = CUser::HASH_NONE;
  		else if (sMethod.Equals("md5"))
  			method = CUser::HASH_MD5;
  		else if (sMethod.Equals("sha256"))
  			method = CUser::HASH_SHA256;
  		else {
  			sError = "Invalid hash method";
  			CUtils::PrintError(sError);
  			return false;
  		}
  
  		SetPass(sHash, method, sSalt);
  		if (!pSubConf->empty()) {
  			sError = "Unhandled lines in config!";
  			CUtils::PrintError(sError);
  
  			CZNC::DumpConfig(pSubConf);
  			return false;
  		}
  		++subIt;
  	}
  	if (subIt != subConf.end()) {
  		sError = "Password defined more than once";
  		CUtils::PrintError(sError);
  		return false;
  	}
  
  	pConfig->FindSubConfig("network", subConf);
  	for (subIt = subConf.begin(); subIt != subConf.end(); ++subIt) {
  		const CString& sNetworkName = subIt->first;
  
  		CUtils::PrintMessage("Loading network [" + sNetworkName + "]");
  
  		CIRCNetwork *pNetwork = FindNetwork(sNetworkName);
  
  		if (!pNetwork) {
  			pNetwork = new CIRCNetwork(this, sNetworkName);
  		}
  
  		if (!pNetwork->ParseConfig(subIt->second.m_pSubConfig, sError)) {
  			return false;
  		}
  	}
  
  	if (pConfig->FindStringVector("server", vsList, false) || pConfig->FindStringVector("chan", vsList, false) || pConfig->FindSubConfig("chan", subConf, false)) {
  		CIRCNetwork *pNetwork = FindNetwork("default");
  		if (!pNetwork) {
  			CString sErrorDummy;
  			pNetwork = AddNetwork("default", sErrorDummy);
  		}
  
  		if (pNetwork) {
  			CUtils::PrintMessage("NOTICE: Found deprecated config, upgrading to a network");
  
  			if (!pNetwork->ParseConfig(pConfig, sError, true)) {
  				return false;
  			}
  		}
  	}
  
  	pConfig->FindStringVector("loadmodule", vsList);
  	for (const CString& sMod : vsList) {
  		CString sModName = sMod.Token(0);
  		CString sNotice = "Loading user module [" + sModName + "]";
  
  		// XXX Legacy crap, added in ZNC 0.089
  		if (sModName == "discon_kick") {
  			sNotice = "NOTICE: [discon_kick] was renamed, loading [disconkick] instead";
  			sModName = "disconkick";
  		}
  
  		// XXX Legacy crap, added in ZNC 0.099
  		if (sModName == "fixfreenode") {
  			sNotice = "NOTICE: [fixfreenode] doesn't do anything useful anymore, ignoring it";
  			CUtils::PrintMessage(sNotice);
  			continue;
  		}
  
  		// XXX Legacy crap, added in ZNC 0.207
  		if (sModName == "admin") {
  			sNotice = "NOTICE: [admin] module was renamed, loading [controlpanel] instead";
  			sModName = "controlpanel";
  		}
  		
  		// XXX Legacy crap, should have been added ZNC 0.207, but added only in 1.1 :(
  		if (sModName == "away") {
  			sNotice = "NOTICE: [away] was renamed, loading [awaystore] instead";
  			sModName = "awaystore";
  		}
  
  		// XXX Legacy crap, added in 1.1; fakeonline module was dropped in 1.0 and returned in 1.1
  		if (sModName == "fakeonline") {
  			sNotice = "NOTICE: [fakeonline] was renamed, loading [modules_online] instead";
  			sModName = "modules_online";
  		}
  
  		// XXX Legacy crap, added in 1.3
  		if (sModName == "charset") {
  			CUtils::PrintAction("NOTICE: Charset support was moved to core, importing old charset module settings");
  			size_t uIndex = 1;
  			if (sMod.Token(uIndex).Equals("-force")) {
  				uIndex++;
  			}
  			VCString vsClient, vsServer;
  			sMod.Token(uIndex).Split(",", vsClient);
  			sMod.Token(uIndex + 1).Split(",", vsServer);
  			if (vsClient.empty() || vsServer.empty()) {
  				CUtils::PrintStatus(false, "charset module was loaded with wrong parameters.");
  				continue;
  			}
  			SetClientEncoding(vsClient[0]);
  			for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
  				pNetwork->SetEncoding(vsServer[0]);
  			}
  			CUtils::PrintStatus(true, "Using [" + vsClient[0] + "] for clients, and [" + vsServer[0] + "] for servers");
  			continue;
  		}
  
  		// XXX Legacy crap, added in 1.7
  		if (sModName == "disconkick") {
  			sNotice = "NOTICE: [disconkick] is integrated to core now, ignoring it";
  			CUtils::PrintMessage(sNotice);
  			continue;
  		}
  
  		CString sModRet;
  		CString sArgs = sMod.Token(1, true);
  
  		bool bModRet = LoadModule(sModName, sArgs, sNotice, sModRet);
  
  		CUtils::PrintStatus(bModRet, sModRet);
  		if (!bModRet) {
  			// XXX The awaynick module was retired in 1.6 (still available as external module)
  			if (sModName == "awaynick") {
  				// load simple_away instead, unless it's already on the list
  				if (std::find(vsList.begin(), vsList.end(), "simple_away") == vsList.end()) {
  					sNotice = "Loading [simple_away] module instead";
  					sModName = "simple_away";
  					// not a fatal error if simple_away is not available
  					LoadModule(sModName, sArgs, sNotice, sModRet);
  				}
  			} else {
  				sError = sModRet;
  				return false;
  			}
  		}
  		continue;
  	}
  
  	// Move ircconnectenabled to the networks
  	if (pConfig->FindStringEntry("ircconnectenabled", sValue)) {
  		for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
  			pNetwork->SetIRCConnectEnabled(sValue.ToBool());
  		}
  	}
  
  	return true;
  }
  
  CIRCNetwork* CUser::AddNetwork(const CString &sNetwork, CString& sErrorRet) {
  	if (!CIRCNetwork::IsValidNetwork(sNetwork)) {
  		sErrorRet = "Invalid network name. It should be alphanumeric. Not to be confused with server name";
  		return nullptr;
  	} else if (FindNetwork(sNetwork)) {
  		sErrorRet = "Network [" + sNetwork.Token(0) + "] already exists";
  		return nullptr;
  	}
  
  	CIRCNetwork* pNetwork = new CIRCNetwork(this, sNetwork);
  
  	bool bCancel = false;
  	USERMODULECALL(OnAddNetwork(*pNetwork, sErrorRet), this, nullptr, &bCancel);
  	if(bCancel) {
  		RemoveNetwork(pNetwork);
  		delete pNetwork;
  		return nullptr;
  	}
  
  	return pNetwork;
  }
  
  bool CUser::AddNetwork(CIRCNetwork *pNetwork) {
  	if (FindNetwork(pNetwork->GetName())) {
  		return false;
  	}
  
  	m_vIRCNetworks.push_back(pNetwork);
  
  	return true;
  }
  
  void CUser::RemoveNetwork(CIRCNetwork *pNetwork) {
  	auto it = std::find(m_vIRCNetworks.begin(), m_vIRCNetworks.end(), pNetwork);
  	if (it != m_vIRCNetworks.end()) {
  		m_vIRCNetworks.erase(it);
  	}
  }
  
  bool CUser::DeleteNetwork(const CString& sNetwork) {
  	CIRCNetwork *pNetwork = FindNetwork(sNetwork);
  
  	if (pNetwork) {
  		bool bCancel = false;
  		USERMODULECALL(OnDeleteNetwork(*pNetwork), this, nullptr, &bCancel);
  		if (!bCancel) {
  			delete pNetwork;
  			return true;
  		}
  	}
  
  	return false;
  }
  
  CIRCNetwork* CUser::FindNetwork(const CString& sNetwork) const {
  	for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
  		if (pNetwork->GetName().Equals(sNetwork)) {
  			return pNetwork;
  		}
  	}
  
  	return nullptr;
  }
  
  const vector<CIRCNetwork*>& CUser::GetNetworks() const {
  	return m_vIRCNetworks;
  }
  
  CString CUser::ExpandString(const CString& sStr) const {
  	CString sRet;
  	return ExpandString(sStr, sRet);
  }
  
  CString& CUser::ExpandString(const CString& sStr, CString& sRet) const {
  	CString sTime = CUtils::CTime(time(nullptr), m_sTimezone);
  
  	sRet = sStr;
  	sRet.Replace("%user%", GetUserName());
  	sRet.Replace("%defnick%", GetNick());
  	sRet.Replace("%nick%", GetNick());
  	sRet.Replace("%altnick%", GetAltNick());
  	sRet.Replace("%ident%", GetIdent());
  	sRet.Replace("%realname%", GetRealName());
  	sRet.Replace("%vhost%", GetBindHost());
  	sRet.Replace("%bindhost%", GetBindHost());
  	sRet.Replace("%version%", CZNC::GetVersion());
  	sRet.Replace("%time%", sTime);
  	sRet.Replace("%uptime%", CZNC::Get().GetUptime());
  	// The following lines do not exist. You must be on DrUgS!
  	sRet.Replace("%znc%", "All your IRC are belong to ZNC");
  	// Chosen by fair zocchihedron dice roll by SilverLeo
  	sRet.Replace("%rand%", "42");
  
  	return sRet;
  }
  
  CString CUser::AddTimestamp(const CString& sStr) const {
  	time_t tm;
  	return AddTimestamp(time(&tm), sStr);
  }
  
  CString CUser::AddTimestamp(time_t tm, const CString& sStr) const {
  	CString sRet = sStr;
  
  	if (!GetTimestampFormat().empty() && (m_bAppendTimestamp || m_bPrependTimestamp)) {
  		CString sTimestamp = CUtils::FormatTime(tm, GetTimestampFormat(), m_sTimezone);
  		if (sTimestamp.empty()) {
  			return sRet;
  		}
  
  		if (m_bPrependTimestamp) {
  			sRet = sTimestamp;
  			sRet += " " + sStr;
  		}
  		if (m_bAppendTimestamp) {
  			// From http://www.mirc.com/colors.html
  			// The Control+O key combination in mIRC inserts ascii character 15,
  			// which turns off all previous attributes, including color, bold, underline, and italics.
  			//
  			// \x02 bold
  			// \x03 mIRC-compatible color
  			// \x04 RRGGBB color
  			// \x0F normal/reset (turn off bold, colors, etc.)
  			// \x12 reverse (weechat)
  			// \x16 reverse (mirc, kvirc)
  			// \x1D italic
  			// \x1F underline
  			// Also see http://www.visualirc.net/tech-attrs.php
  			//
  			// Keep in sync with CIRCSocket::IcuExt__UCallback
  			if (CString::npos != sRet.find_first_of("\x02\x03\x04\x0F\x12\x16\x1D\x1F")) {
  				sRet += "\x0F";
  			}
  
  			sRet += " " + sTimestamp;
  		}
  	}
  
  	return sRet;
  }
  
  void CUser::BounceAllClients() {
  	for (CClient* pClient : m_vClients) {
  		pClient->BouncedOff();
  	}
  
  	m_vClients.clear();
  }
  
  void CUser::UserConnected(CClient* pClient) {
  	if (!MultiClients()) {
  		BounceAllClients();
  	}
  
  	pClient->PutClient(":irc.znc.in 001 " + pClient->GetNick() + " :- Welcome to ZNC -");
  
  	m_vClients.push_back(pClient);
  }
  
  void CUser::UserDisconnected(CClient* pClient) {
  	auto it = std::find(m_vClients.begin(), m_vClients.end(), pClient);
  	if (it != m_vClients.end()) {
  		m_vClients.erase(it);
  	}
  }
  
  void CUser::CloneNetworks(const CUser& User) {
  	const vector<CIRCNetwork*>& vNetworks = User.GetNetworks();
  	for (CIRCNetwork* pUserNetwork : vNetworks) {
  		CIRCNetwork *pNetwork = FindNetwork(pUserNetwork->GetName());
  
  		if (pNetwork) {
  			pNetwork->Clone(*pUserNetwork);
  		} else {
  			new CIRCNetwork(this, *pUserNetwork);
  		}
  	}
  
  	set<CString> ssDeleteNetworks;
  	for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
  		if (!(User.FindNetwork(pNetwork->GetName()))) {
  			ssDeleteNetworks.insert(pNetwork->GetName());
  		}
  	}
  
  	for (const CString& sNetwork : ssDeleteNetworks) {
  		// The following will move all the clients to the user.
  		// So the clients are not disconnected. The client could
  		// have requested the rehash. Then when we do
  		// client->PutStatus("Rehashing succeeded!") we would
  		// crash if there was no client anymore.
  		const vector<CClient*>& vClients = FindNetwork(sNetwork)->GetClients();
  
  		while (vClients.begin() != vClients.end()) {
  			CClient *pClient = vClients.front();
  			// This line will remove pClient from vClients,
  			// because it's a reference to the internal Network's vector.
  			pClient->SetNetwork(nullptr);
  		}
  
  		DeleteNetwork(sNetwork);
  	}
  }
  
  bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneNetworks) {
  	sErrorRet.clear();
  
  	if (!User.IsValid(sErrorRet, true)) {
  		return false;
  	}
  
  	// user names can only specified for the constructor, changing it later
  	// on breaks too much stuff (e.g. lots of paths depend on the user name)
  	if (GetUserName() != User.GetUserName()) {
  		DEBUG("Ignoring username in CUser::Clone(), old username [" << GetUserName()
  				<< "]; New username [" << User.GetUserName() << "]");
  	}
  
  	if (!User.GetPass().empty()) {
  		SetPass(User.GetPass(), User.GetPassHashType(), User.GetPassSalt());
  	}
  
  	SetNick(User.GetNick(false));
  	SetAltNick(User.GetAltNick(false));
  	SetIdent(User.GetIdent(false));
  	SetRealName(User.GetRealName());
  	SetStatusPrefix(User.GetStatusPrefix());
  	SetBindHost(User.GetBindHost());
  	SetDCCBindHost(User.GetDCCBindHost());
  	SetQuitMsg(User.GetQuitMsg());
  	SetSkinName(User.GetSkinName());
  	SetDefaultChanModes(User.GetDefaultChanModes());
  	SetBufferCount(User.GetBufferCount(), true);
  	SetJoinTries(User.JoinTries());
  	SetMaxNetworks(User.MaxNetworks());
  	SetMaxQueryBuffers(User.MaxQueryBuffers());
  	SetMaxJoins(User.MaxJoins());
  	SetClientEncoding(User.GetClientEncoding());
  
  	// Allowed Hosts
  	m_ssAllowedHosts.clear();
  	const set<CString>& ssHosts = User.GetAllowedHosts();
  	for (const CString& sHost : ssHosts) {
  		AddAllowedHost(sHost);
  	}
  
  	for (CClient* pSock : m_vClients) {
  		if (!IsHostAllowed(pSock->GetRemoteIP())) {
  			pSock->PutStatusNotice("You are being disconnected because your IP is no longer allowed to connect to this user");
  			pSock->Close();
  		}
  	}
  
  	// !Allowed Hosts
  
  	// Networks
  	if (bCloneNetworks) {
  		CloneNetworks(User);
  	}
  	// !Networks
  
  	// CTCP Replies
  	m_mssCTCPReplies.clear();
  	const MCString& msReplies = User.GetCTCPReplies();
  	for (const auto& it : msReplies) {
  		AddCTCPReply(it.first, it.second);
  	}
  	// !CTCP Replies
  
  	// Flags
  	SetAutoClearChanBuffer(User.AutoClearChanBuffer());
  	SetAutoClearQueryBuffer(User.AutoClearQueryBuffer());
  	SetMultiClients(User.MultiClients());
  	SetDenyLoadMod(User.DenyLoadMod());
  	SetAdmin(User.IsAdmin());
  	SetDenySetBindHost(User.DenySetBindHost());
  	SetTimestampAppend(User.GetTimestampAppend());
  	SetTimestampPrepend(User.GetTimestampPrepend());
  	SetTimestampFormat(User.GetTimestampFormat());
  	SetTimezone(User.GetTimezone());
  	// !Flags
  
  	// Modules
  	set<CString> ssUnloadMods;
  	CModules& vCurMods = GetModules();
  	const CModules& vNewMods = User.GetModules();
  
  	for (CModule* pNewMod : vNewMods) {
  		CString sModRet;
  		CModule* pCurMod = vCurMods.FindModule(pNewMod->GetModName());
  
  		if (!pCurMod) {
  			vCurMods.LoadModule(pNewMod->GetModName(), pNewMod->GetArgs(), CModInfo::UserModule, this, nullptr, sModRet);
  		} else if (pNewMod->GetArgs() != pCurMod->GetArgs()) {
  			vCurMods.ReloadModule(pNewMod->GetModName(), pNewMod->GetArgs(), this, nullptr, sModRet);
  		}
  	}
  
  	for (CModule* pCurMod : vCurMods) {
  		CModule* pNewMod = vNewMods.FindModule(pCurMod->GetModName());
  
  		if (!pNewMod) {
  			ssUnloadMods.insert(pCurMod->GetModName());
  		}
  	}
  
  	for (const CString& sMod : ssUnloadMods) {
  		vCurMods.UnloadModule(sMod);
  	}
  	// !Modules
  
  	return true;
  }
  
  const set<CString>& CUser::GetAllowedHosts() const { return m_ssAllowedHosts; }
  bool CUser::AddAllowedHost(const CString& sHostMask) {
  	if (sHostMask.empty() || m_ssAllowedHosts.find(sHostMask) != m_ssAllowedHosts.end()) {
  		return false;
  	}
  
  	m_ssAllowedHosts.insert(sHostMask);
  	return true;
  }
  
  bool CUser::IsHostAllowed(const CString& sHostMask) const {
  	if (m_ssAllowedHosts.empty()) {
  		return true;
  	}
  
  	for (const CString& sHost : m_ssAllowedHosts) {
  		if (sHostMask.WildCmp(sHost)) {
  			return true;
  		}
  	}
  
  	return false;
  }
  
  const CString& CUser::GetTimestampFormat() const { return m_sTimestampFormat; }
  bool CUser::GetTimestampAppend() const { return m_bAppendTimestamp; }
  bool CUser::GetTimestampPrepend() const { return m_bPrependTimestamp; }
  
  bool CUser::IsValidUserName(const CString& sUserName) {
  	// /^[a-zA-Z][a-zA-Z@._\-]*$/
  	const char* p = sUserName.c_str();
  
  	if (sUserName.empty()) {
  		return false;
  	}
  
  	if ((*p < 'a' || *p > 'z') && (*p < 'A' || *p > 'Z')) {
  		return false;
  	}
  
  	while (*p) {
  		if (*p != '@' && *p != '.' && *p != '-' && *p != '_' && !isalnum(*p)) {
  			return false;
  		}
  
  		p++;
  	}
  
  	return true;
  }
  
  bool CUser::IsValid(CString& sErrMsg, bool bSkipPass) const {
  	sErrMsg.clear();
  
  	if (!bSkipPass && m_sPass.empty()) {
  		sErrMsg = "Pass is empty";
  		return false;
  	}
  
  	if (m_sUserName.empty()) {
  		sErrMsg = "Username is empty";
  		return false;
  	}
  
  	if (!CUser::IsValidUserName(m_sUserName)) {
  		sErrMsg = "Username is invalid";
  		return false;
  	}
  
  	return true;
  }
  
  CConfig CUser::ToConfig() const {
  	CConfig config;
  	CConfig passConfig;
  
  	CString sHash;
  	switch (m_eHashType) {
  	case HASH_NONE:
  		sHash = "Plain";
  		break;
  	case HASH_MD5:
  		sHash = "MD5";
  		break;
  	case HASH_SHA256:
  		sHash = "SHA256";
  		break;
  	}
  	passConfig.AddKeyValuePair("Salt", m_sPassSalt);
  	passConfig.AddKeyValuePair("Method", sHash);
  	passConfig.AddKeyValuePair("Hash", GetPass());
  	config.AddSubConfig("Pass", "password", passConfig);
  
  	config.AddKeyValuePair("Nick", GetNick());
  	config.AddKeyValuePair("AltNick", GetAltNick());
  	config.AddKeyValuePair("Ident", GetIdent());
  	config.AddKeyValuePair("RealName", GetRealName());
  	config.AddKeyValuePair("BindHost", GetBindHost());
  	config.AddKeyValuePair("DCCBindHost", GetDCCBindHost());
  	config.AddKeyValuePair("QuitMsg", GetQuitMsg());
  	if (CZNC::Get().GetStatusPrefix() != GetStatusPrefix())
  		config.AddKeyValuePair("StatusPrefix", GetStatusPrefix());
  	config.AddKeyValuePair("Skin", GetSkinName());
  	config.AddKeyValuePair("ChanModes", GetDefaultChanModes());
  	config.AddKeyValuePair("Buffer", CString(GetBufferCount()));
  	config.AddKeyValuePair("AutoClearChanBuffer", CString(AutoClearChanBuffer()));
  	config.AddKeyValuePair("AutoClearQueryBuffer", CString(AutoClearQueryBuffer()));
  	config.AddKeyValuePair("MultiClients", CString(MultiClients()));
  	config.AddKeyValuePair("DenyLoadMod", CString(DenyLoadMod()));
  	config.AddKeyValuePair("Admin", CString(IsAdmin()));
  	config.AddKeyValuePair("DenySetBindHost", CString(DenySetBindHost()));
  	config.AddKeyValuePair("TimestampFormat", GetTimestampFormat());
  	config.AddKeyValuePair("AppendTimestamp", CString(GetTimestampAppend()));
  	config.AddKeyValuePair("PrependTimestamp", CString(GetTimestampPrepend()));
  	config.AddKeyValuePair("Timezone", m_sTimezone);
  	config.AddKeyValuePair("JoinTries", CString(m_uMaxJoinTries));
  	config.AddKeyValuePair("MaxNetworks", CString(m_uMaxNetworks));
  	config.AddKeyValuePair("MaxQueryBuffers", CString(m_uMaxQueryBuffers));
  	config.AddKeyValuePair("MaxJoins", CString(m_uMaxJoins));
  	config.AddKeyValuePair("ClientEncoding", GetClientEncoding());
  
  	// Allow Hosts
  	if (!m_ssAllowedHosts.empty()) {
  		for (const CString& sHost : m_ssAllowedHosts) {
  			config.AddKeyValuePair("Allow", sHost);
  		}
  	}
  
  	// CTCP Replies
  	if (!m_mssCTCPReplies.empty()) {
  		for (const auto& itb : m_mssCTCPReplies) {
  			config.AddKeyValuePair("CTCPReply", itb.first.AsUpper() + " " + itb.second);
  		}
  	}
  
  	// Modules
  	const CModules& Mods = GetModules();
  
  	if (!Mods.empty()) {
  		for (CModule* pMod : Mods) {
  			CString sArgs = pMod->GetArgs();
  
  			if (!sArgs.empty()) {
  				sArgs = " " + sArgs;
  			}
  
  			config.AddKeyValuePair("LoadModule", pMod->GetModName() + sArgs);
  		}
  	}
  
  	// Networks
  	for (CIRCNetwork *pNetwork : m_vIRCNetworks) {
  		config.AddSubConfig("Network", pNetwork->GetName(), pNetwork->ToConfig());
  	}
  
  	return config;
  }
  
  bool CUser::CheckPass(const CString& sPass) const {
  	switch (m_eHashType)
  	{
  	case HASH_MD5:
  		return m_sPass.Equals(CUtils::SaltedMD5Hash(sPass, m_sPassSalt));
  	case HASH_SHA256:
  		return m_sPass.Equals(CUtils::SaltedSHA256Hash(sPass, m_sPassSalt));
  	case HASH_NONE:
  	default:
  		return (sPass == m_sPass);
  	}
  }
  
  /*CClient* CUser::GetClient() {
  	// Todo: optimize this by saving a pointer to the sock
  	CSockManager& Manager = CZNC::Get().GetManager();
  	CString sSockName = "USR::" + m_sUserName;
  
  	for (unsigned int a = 0; a < Manager.size(); a++) {
  		Csock* pSock = Manager[a];
  		if (pSock->GetSockName().Equals(sSockName)) {
  			if (!pSock->IsClosed()) {
  				return (CClient*) pSock;
  			}
  		}
  	}
  
  	return (CClient*) CZNC::Get().GetManager().FindSockByName(sSockName);
  }*/
  
  CString CUser::GetLocalDCCIP() const {
  	if (!GetDCCBindHost().empty())
  		return GetDCCBindHost();
  
  	for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
  		CIRCSock* pIRCSock = pNetwork->GetIRCSock();
  		if (pIRCSock) {
  			return pIRCSock->GetLocalIP();
  		}
  	}
  
  	if (!GetAllClients().empty()) {
  		return GetAllClients()[0]->GetLocalIP();
  	}
  
  	return "";
  }
  
  bool CUser::PutUser(const CString& sLine, CClient* pClient, CClient* pSkipClient) {
  	for (CClient* pEachClient : m_vClients) {
  		if ((!pClient || pClient == pEachClient) && pSkipClient != pEachClient) {
  			pEachClient->PutClient(sLine);
  
  			if (pClient) {
  				return true;
  			}
  		}
  	}
  
  	return (pClient == nullptr);
  }
  
  bool CUser::PutAllUser(const CString& sLine, CClient* pClient, CClient* pSkipClient) {
  	PutUser(sLine, pClient, pSkipClient);
  
  	for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
  		if (pNetwork->PutUser(sLine, pClient, pSkipClient)) {
  			return true;
  		}
  	}
  
  	return (pClient == nullptr);
  }
  
  bool CUser::PutStatus(const CString& sLine, CClient* pClient, CClient* pSkipClient) {
  	vector<CClient*> vClients = GetAllClients();
  	for (CClient* pEachClient : vClients) {
  		if ((!pClient || pClient == pEachClient) && pSkipClient != pEachClient) {
  			pEachClient->PutStatus(sLine);
  
  			if (pClient) {
  				return true;
  			}
  		}
  	}
  
  	return (pClient == nullptr);
  }
  
  bool CUser::PutStatusNotice(const CString& sLine, CClient* pClient, CClient* pSkipClient) {
  	vector<CClient*> vClients = GetAllClients();
  	for (CClient* pEachClient : vClients) {
  		if ((!pClient || pClient == pEachClient) && pSkipClient != pEachClient) {
  			pEachClient->PutStatusNotice(sLine);
  
  			if (pClient) {
  				return true;
  			}
  		}
  	}
  
  	return (pClient == nullptr);
  }
  
  bool CUser::PutModule(const CString& sModule, const CString& sLine, CClient* pClient, CClient* pSkipClient) {
  	for (CClient* pEachClient : m_vClients) {
  		if ((!pClient || pClient == pEachClient) && pSkipClient != pEachClient) {
  			pEachClient->PutModule(sModule, sLine);
  
  			if (pClient) {
  				return true;
  			}
  		}
  	}
  
  	return (pClient == nullptr);
  }
  
  bool CUser::PutModNotice(const CString& sModule, const CString& sLine, CClient* pClient, CClient* pSkipClient) {
  	for (CClient* pEachClient : m_vClients) {
  		if ((!pClient || pClient == pEachClient) && pSkipClient != pEachClient) {
  			pEachClient->PutModNotice(sModule, sLine);
  
  			if (pClient) {
  				return true;
  			}
  		}
  	}
  
  	return (pClient == nullptr);
  }
  
  CString CUser::MakeCleanUserName(const CString& sUserName) {
  	return sUserName.Token(0, false, "@").Replace_n(".", "");
  }
  
  bool CUser::IsUserAttached() const {
  	if (!m_vClients.empty()) {
  		return true;
  	}
  
  	for (const CIRCNetwork* pNetwork : m_vIRCNetworks) {
  		if (pNetwork->IsUserAttached()) {
  			return true;
  		}
  	}
  
  	return false;
  }
  
  bool CUser::LoadModule(const CString& sModName, const CString& sArgs, const CString& sNotice, CString& sError)
  {
  	bool bModRet = true;
  	CString sModRet;
  
  	CModInfo ModInfo;
  	if (!CZNC::Get().GetModules().GetModInfo(ModInfo, sModName, sModRet)) {
  		sError = "Unable to find modinfo [" + sModName + "] [" + sModRet + "]";
  		return false;
  	}
  
  	CUtils::PrintAction(sNotice);
  
  	if (!ModInfo.SupportsType(CModInfo::UserModule) && ModInfo.SupportsType(CModInfo::NetworkModule)) {
  		CUtils::PrintMessage("NOTICE: Module [" + sModName + "] is a network module, loading module for all networks in user.");
  
  		// Do they have old NV?
  		CFile fNVFile = CFile(GetUserPath() + "/moddata/" + sModName + "/.registry");
  
  		for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
  			// Check whether the network already has this module loaded (#954)
  			if (pNetwork->GetModules().FindModule(sModName)) {
  				continue;
  			}
  
  			if (fNVFile.Exists()) {
  				CString sNetworkModPath = pNetwork->GetNetworkPath() + "/moddata/" + sModName;
  				if (!CFile::Exists(sNetworkModPath)) {
  					CDir::MakeDir(sNetworkModPath);
  				}
  
  				fNVFile.Copy(sNetworkModPath + "/.registry");
  			}
  
  			bModRet = pNetwork->GetModules().LoadModule(sModName, sArgs, CModInfo::NetworkModule, this, pNetwork, sModRet);
  			if (!bModRet) {
  				break;
  			}
  		}
  	} else {
  		bModRet = GetModules().LoadModule(sModName, sArgs, CModInfo::UserModule, this, nullptr, sModRet);
  	}
  
  	if (!bModRet) {
  		sError = sModRet;
  	}
  	return bModRet;
  }
  
  // Setters
  void CUser::SetNick(const CString& s) { m_sNick = s; }
  void CUser::SetAltNick(const CString& s) { m_sAltNick = s; }
  void CUser::SetIdent(const CString& s) { m_sIdent = s; }
  void CUser::SetRealName(const CString& s) { m_sRealName = s; }
  void CUser::SetBindHost(const CString& s) { m_sBindHost = s; }
  void CUser::SetDCCBindHost(const CString& s) { m_sDCCBindHost = s; }
  void CUser::SetPass(const CString& s, eHashType eHash, const CString& sSalt) {
  	m_sPass = s;
  	m_eHashType = eHash;
  	m_sPassSalt = sSalt;
  }
  void CUser::SetMultiClients(bool b) { m_bMultiClients = b; }
  void CUser::SetDenyLoadMod(bool b) { m_bDenyLoadMod = b; }
  void CUser::SetAdmin(bool b) { m_bAdmin = b; }
  void CUser::SetDenySetBindHost(bool b) { m_bDenySetBindHost = b; }
  void CUser::SetDefaultChanModes(const CString& s) { m_sDefaultChanModes = s; }
  void CUser::SetClientEncoding(const CString& s) { m_sClientEncoding = s; }
  void CUser::SetQuitMsg(const CString& s) { m_sQuitMsg = s; }
  void CUser::SetAutoClearChanBuffer(bool b) {
  	for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
  		for (CChan* pChan : pNetwork->GetChans()) {
  			pChan->InheritAutoClearChanBuffer(b);
  		}
  	}
  	m_bAutoClearChanBuffer = b;
  }
  void CUser::SetAutoClearQueryBuffer(bool b) { m_bAutoClearQueryBuffer = b; }
  
  bool CUser::SetBufferCount(unsigned int u, bool bForce) {
  	if (!bForce && u > CZNC::Get().GetMaxBufferSize())
  		return false;
  	for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
  		for (CChan* pChan : pNetwork->GetChans()) {
  			pChan->InheritBufferCount(u, bForce);
  		}
  	}
  	m_uBufferCount = u;
  	return true;
  }
  
  bool CUser::AddCTCPReply(const CString& sCTCP, const CString& sReply) {
  	// Reject CTCP requests containing spaces
  	if (sCTCP.find_first_of(' ') != CString::npos) {
  		return false;
  	}
  	// Reject empty CTCP requests
  	if (sCTCP.empty()) {
  		return false;
  	}
  	m_mssCTCPReplies[sCTCP.AsUpper()] = sReply;
  	return true;
  }
  
  bool CUser::DelCTCPReply(const CString& sCTCP) {
  	return m_mssCTCPReplies.erase(sCTCP) > 0;
  }
  
  bool CUser::SetStatusPrefix(const CString& s) {
  	if ((!s.empty()) && (s.length() < 6) && (s.find(' ') == CString::npos)) {
  		m_sStatusPrefix = (s.empty()) ? "*" : s;
  		return true;
  	}
  
  	return false;
  }
  // !Setters
  
  // Getters
  vector<CClient*> CUser::GetAllClients() const {
  	vector<CClient*> vClients;
  
  	for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
  		for (CClient* pClient : pNetwork->GetClients()) {
  			vClients.push_back(pClient);
  		}
  	}
  
  	for (CClient* pClient : m_vClients) {
  		vClients.push_back(pClient);
  	}
  
  	return vClients;
  }
  
  const CString& CUser::GetUserName() const { return m_sUserName; }
  const CString& CUser::GetCleanUserName() const { return m_sCleanUserName; }
  const CString& CUser::GetNick(bool bAllowDefault) const { return (bAllowDefault && m_sNick.empty()) ? GetCleanUserName() : m_sNick; }
  const CString& CUser::GetAltNick(bool bAllowDefault) const { return (bAllowDefault && m_sAltNick.empty()) ? GetCleanUserName() : m_sAltNick; }
  const CString& CUser::GetIdent(bool bAllowDefault) const { return (bAllowDefault && m_sIdent.empty()) ? GetCleanUserName() : m_sIdent; }
  const CString& CUser::GetRealName() const { return m_sRealName.empty() ? m_sUserName : m_sRealName; }
  const CString& CUser::GetBindHost() const { return m_sBindHost; }
  const CString& CUser::GetDCCBindHost() const { return m_sDCCBindHost; }
  const CString& CUser::GetPass() const { return m_sPass; }
  CUser::eHashType CUser::GetPassHashType() const { return m_eHashType; }
  const CString& CUser::GetPassSalt() const { return m_sPassSalt; }
  bool CUser::DenyLoadMod() const { return m_bDenyLoadMod; }
  bool CUser::IsAdmin() const { return m_bAdmin; }
  bool CUser::DenySetBindHost() const { return m_bDenySetBindHost; }
  bool CUser::MultiClients() const { return m_bMultiClients; }
  const CString& CUser::GetStatusPrefix() const { return m_sStatusPrefix; }
  const CString& CUser::GetDefaultChanModes() const { return m_sDefaultChanModes; }
  const CString& CUser::GetClientEncoding() const { return m_sClientEncoding; }
  bool CUser::HasSpaceForNewNetwork() const { return GetNetworks().size() < MaxNetworks(); }
  
  CString CUser::GetQuitMsg() const { return (!m_sQuitMsg.Trim_n().empty()) ? m_sQuitMsg : CZNC::GetTag(false); }
  const MCString& CUser::GetCTCPReplies() const { return m_mssCTCPReplies; }
  unsigned int CUser::GetBufferCount() const { return m_uBufferCount; }
  bool CUser::AutoClearChanBuffer() const { return m_bAutoClearChanBuffer; }
  bool CUser::AutoClearQueryBuffer() const { return m_bAutoClearQueryBuffer; }
  //CString CUser::GetSkinName() const { return (!m_sSkinName.empty()) ? m_sSkinName : CZNC::Get().GetSkinName(); }
  CString CUser::GetSkinName() const { return m_sSkinName; }
  const CString& CUser::GetUserPath() const { if (!CFile::Exists(m_sUserPath)) { CDir::MakeDir(m_sUserPath); } return m_sUserPath; }
  // !Getters
  