mirror of
https://github.com/znc/znc.git
synced 2026-03-28 17:42:41 +01:00
1515 lines
49 KiB
C++
1515 lines
49 KiB
C++
/*
|
|
* Copyright (C) 2004-2025 ZNC, see the NOTICE file for details.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <znc/User.h>
|
|
#include <znc/Config.h>
|
|
#include <znc/FileUtils.h>
|
|
#include <znc/IRCNetwork.h>
|
|
#include <znc/IRCSock.h>
|
|
#include <znc/Chan.h>
|
|
#include <znc/Query.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <algorithm>
|
|
|
|
#ifdef ZNC_HAVE_ARGON
|
|
#include <argon2.h>
|
|
#endif
|
|
|
|
using std::vector;
|
|
using std::set;
|
|
|
|
class CUserTimer : public CCron {
|
|
public:
|
|
CUserTimer(CUser* pUser) : CCron(), m_pUser(pUser) {
|
|
SetName("CUserTimer::" + m_pUser->GetUsername());
|
|
Start(m_pUser->GetPingSlack());
|
|
}
|
|
~CUserTimer() override {}
|
|
|
|
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() >=
|
|
m_pUser->GetPingFrequency()) {
|
|
pUserClient->PutClient("PING :ZNC");
|
|
}
|
|
}
|
|
|
|
// Restart timer for the case if the period had changed. Usually this is
|
|
// noop
|
|
Start(m_pUser->GetPingSlack());
|
|
}
|
|
|
|
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(""),
|
|
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_bDenySetIdent(false),
|
|
m_bDenySetNetwork(false),
|
|
m_bDenySetRealName(false),
|
|
m_bDenySetQuitMsg(false),
|
|
m_bDenySetCTCPReplies(false),
|
|
m_bAutoClearChanBuffer(true),
|
|
m_bAutoClearQueryBuffer(true),
|
|
m_bBeingDeleted(false),
|
|
m_bAppendTimestamp(false),
|
|
m_bPrependTimestamp(true),
|
|
m_bAuthOnlyViaModule(false),
|
|
m_pUserTimer(nullptr),
|
|
m_vIRCNetworks(),
|
|
m_vClients(),
|
|
m_ssAllowedHosts(),
|
|
m_uChanBufferSize(50),
|
|
m_uQueryBufferSize(50),
|
|
m_uBytesRead(0),
|
|
m_uBytesWritten(0),
|
|
m_uMaxJoinTries(10),
|
|
m_uMaxNetworks(1),
|
|
m_uMaxQueryBuffers(50),
|
|
m_uMaxJoins(0),
|
|
m_uNoTrafficTimeout(180),
|
|
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(m_uBytesRead);
|
|
CZNC::Get().AddBytesWritten(m_uBytesWritten);
|
|
}
|
|
|
|
namespace {
|
|
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},
|
|
{"notraffictimeout", &CUser::SetNoTrafficTimeout},
|
|
};
|
|
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},
|
|
{"denysetident", &CUser::SetDenySetIdent},
|
|
{"denysetnetwork", &CUser::SetDenySetNetwork},
|
|
{"denysetrealname", &CUser::SetDenySetRealName},
|
|
{"denysetquitmsg", &CUser::SetDenySetQuitMsg},
|
|
{"denysetctcpreplies", &CUser::SetDenySetCTCPReplies},
|
|
{"appendtimestamp", &CUser::SetTimestampAppend},
|
|
{"prependtimestamp", &CUser::SetTimestampPrepend},
|
|
{"authonlyviamodule", &CUser::SetAuthOnlyViaModule},
|
|
};
|
|
|
|
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("chanbuffersize", sValue))
|
|
SetChanBufferSize(sValue.ToUInt(), true);
|
|
if (pConfig->FindStringEntry("querybuffersize", sValue))
|
|
SetQueryBufferSize(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);
|
|
}
|
|
}
|
|
}
|
|
if (pConfig->FindStringEntry("language", sValue)) {
|
|
SetLanguage(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.TrimSuffix("-")) {
|
|
SetPass(sValue.Trim_n(), 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 if (sMethod.Equals("Argon2id")) {
|
|
method = CUser::HASH_ARGON2ID;
|
|
#ifndef ZNC_HAVE_ARGON
|
|
CUtils::PrintError("ZNC is built without Argon2 support, " + GetUsername() + " won't be able to authenticate");
|
|
#endif
|
|
} 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;
|
|
}
|
|
|
|
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 =
|
|
t_s("Invalid network name. It should be alphanumeric. Not to be "
|
|
"confused with server name");
|
|
return nullptr;
|
|
} else if (FindNetwork(sNetwork)) {
|
|
sErrorRet = t_f("Network {1} already exists")(sNetwork);
|
|
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("%altnick%", GetAltNick());
|
|
sRet.Replace("%bindhost%", GetBindHost());
|
|
sRet.Replace("%defnick%", GetNick());
|
|
sRet.Replace("%ident%", GetIdent());
|
|
sRet.Replace("%nick%", GetNick());
|
|
sRet.Replace("%realname%", GetRealName());
|
|
sRet.Replace("%time%", sTime);
|
|
sRet.Replace("%uptime%", CZNC::Get().GetUptime());
|
|
sRet.Replace("%user%", GetUsername());
|
|
sRet.Replace("%version%", CZNC::GetVersion());
|
|
sRet.Replace("%vhost%", GetBindHost());
|
|
sRet.Replace("%znc%", CZNC::GetTag(false));
|
|
|
|
// Allows for escaping ExpandString if necessary, or to prevent
|
|
// defaults from kicking in if you don't want them.
|
|
sRet.Replace("%empty%", "");
|
|
// The following lines do not exist. You must be on DrUgS!
|
|
sRet.Replace("%irc%", "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 {
|
|
timeval tv;
|
|
gettimeofday(&tv, nullptr);
|
|
return AddTimestamp(tv, sStr);
|
|
}
|
|
|
|
CString CUser::AddTimestamp(time_t tm, const CString& sStr) const {
|
|
timeval tv;
|
|
tv.tv_sec = tm;
|
|
tv.tv_usec = 0;
|
|
return AddTimestamp(tv, sStr);
|
|
}
|
|
|
|
CString CUser::AddTimestamp(timeval tv, const CString& sStr) const {
|
|
CString sRet = sStr;
|
|
|
|
if (!GetTimestampFormat().empty() &&
|
|
(m_bAppendTimestamp || m_bPrependTimestamp)) {
|
|
CString sTimestamp =
|
|
CUtils::FormatTime(tv, 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() + " :" +
|
|
t_s("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());
|
|
SetChanBufferSize(User.GetChanBufferSize(), true);
|
|
SetQueryBufferSize(User.GetQueryBufferSize(), true);
|
|
SetJoinTries(User.JoinTries());
|
|
SetMaxNetworks(User.MaxNetworks());
|
|
SetMaxQueryBuffers(User.MaxQueryBuffers());
|
|
SetMaxJoins(User.MaxJoins());
|
|
SetNoTrafficTimeout(User.GetNoTrafficTimeout());
|
|
SetClientEncoding(User.GetClientEncoding());
|
|
SetLanguage(User.GetLanguage());
|
|
|
|
// 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(
|
|
t_s("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());
|
|
SetDenySetIdent(User.DenySetIdent());
|
|
SetDenySetNetwork(User.DenySetNetwork());
|
|
SetDenySetRealName(User.DenySetRealName());
|
|
SetDenySetQuitMsg(User.DenySetQuitMsg());
|
|
SetDenySetCTCPReplies(User.DenySetCTCPReplies());
|
|
SetAuthOnlyViaModule(User.AuthOnlyViaModule());
|
|
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::RemAllowedHost(const CString& sHostMask) {
|
|
return m_ssAllowedHosts.erase(sHostMask) > 0;
|
|
}
|
|
void CUser::ClearAllowedHosts() { m_ssAllowedHosts.clear(); }
|
|
|
|
bool CUser::IsHostAllowed(const CString& sHost) const {
|
|
if (m_ssAllowedHosts.empty()) {
|
|
return true;
|
|
}
|
|
|
|
for (const CString& sAllowedHost : m_ssAllowedHosts) {
|
|
if (CUtils::CheckCIDR(sHost, sAllowedHost)) {
|
|
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) {
|
|
return CUser::IsValidUserName(sUsername);
|
|
}
|
|
|
|
/// @deprecated
|
|
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 = t_s("Password is empty");
|
|
return false;
|
|
}
|
|
|
|
if (m_sUsername.empty()) {
|
|
sErrMsg = t_s("Username is empty");
|
|
return false;
|
|
}
|
|
|
|
if (!CUser::IsValidUsername(m_sUsername)) {
|
|
sErrMsg = t_s("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;
|
|
case HASH_ARGON2ID:
|
|
sHash = "Argon2id";
|
|
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("ChanBufferSize", CString(GetChanBufferSize()));
|
|
config.AddKeyValuePair("QueryBufferSize", CString(GetQueryBufferSize()));
|
|
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("DenySetIdent", CString(DenySetIdent()));
|
|
config.AddKeyValuePair("DenySetNetwork", CString(DenySetNetwork()));
|
|
config.AddKeyValuePair("DenySetRealName", CString(DenySetRealName()));
|
|
config.AddKeyValuePair("DenySetQuitMsg", CString(DenySetQuitMsg()));
|
|
config.AddKeyValuePair("DenySetCTCPReplies", CString(DenySetCTCPReplies()));
|
|
config.AddKeyValuePair("TimestampFormat", GetTimestampFormat());
|
|
config.AddKeyValuePair("AppendTimestamp", CString(GetTimestampAppend()));
|
|
config.AddKeyValuePair("PrependTimestamp", CString(GetTimestampPrepend()));
|
|
config.AddKeyValuePair("AuthOnlyViaModule", CString(AuthOnlyViaModule()));
|
|
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());
|
|
config.AddKeyValuePair("Language", GetLanguage());
|
|
config.AddKeyValuePair("NoTrafficTimeout", CString(GetNoTrafficTimeout()));
|
|
|
|
// 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) {
|
|
if(AuthOnlyViaModule() || CZNC::Get().GetAuthOnlyViaModule()) {
|
|
return false;
|
|
}
|
|
|
|
bool bResult = false;
|
|
bool bUpgrade = false;
|
|
switch (m_eHashType) {
|
|
case HASH_MD5:
|
|
bResult = m_sPass.Equals(CUtils::SaltedMD5Hash(sPass, m_sPassSalt));
|
|
bUpgrade = true;
|
|
break;
|
|
case HASH_SHA256:
|
|
bResult = m_sPass.Equals(CUtils::SaltedSHA256Hash(sPass, m_sPassSalt));
|
|
#if ZNC_HAVE_ARGON
|
|
bUpgrade = true;
|
|
#endif
|
|
break;
|
|
case HASH_ARGON2ID:
|
|
#if ZNC_HAVE_ARGON
|
|
return argon2id_verify(m_sPass.c_str(), sPass.data(), sPass.length()) == ARGON2_OK;
|
|
#else
|
|
CUtils::PrintError("ZNC is built without Argon2 support, " + GetUsername() + " cannot authenticate");
|
|
return false;
|
|
#endif
|
|
case HASH_NONE:
|
|
// Don't upgrade hash, since the only valid use case for plain are
|
|
// manual tests, where it's simpler this way
|
|
return (sPass == m_sPass);
|
|
}
|
|
|
|
if (bResult && bUpgrade) {
|
|
CString sSalt = CUtils::GetSalt();
|
|
CString sHash = CUser::SaltedHash(sPass, sSalt);
|
|
SetPass(sHash, CUser::HASH_DEFAULT, sSalt);
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
/*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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 : GetAllClients()) {
|
|
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 : GetAllClients()) {
|
|
if ((!pClient || pClient == pEachClient) &&
|
|
pSkipClient != pEachClient) {
|
|
pEachClient->PutModNotice(sModule, sLine);
|
|
|
|
if (pClient) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (pClient == nullptr);
|
|
}
|
|
|
|
|
|
CString CUser::MakeCleanUsername(const CString& sUsername) {
|
|
return CUser::MakeCleanUserName(sUsername);
|
|
}
|
|
|
|
/// @deprecated
|
|
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 = t_f("Unable to find modinfo {1}: {2}")(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;
|
|
switch (eHash) {
|
|
case HASH_NONE:
|
|
case HASH_ARGON2ID:
|
|
// Salt is embedded in the encoded "hash" in argon
|
|
m_sPassSalt = "";
|
|
break;
|
|
case HASH_MD5:
|
|
case HASH_SHA256:
|
|
m_sPassSalt = sSalt;
|
|
break;
|
|
}
|
|
}
|
|
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::SetDenySetIdent(bool b) { m_bDenySetIdent = b; }
|
|
void CUser::SetDenySetNetwork(bool b) { m_bDenySetNetwork = b; }
|
|
void CUser::SetDenySetRealName(bool b) { m_bDenySetRealName = b; }
|
|
void CUser::SetDenySetQuitMsg(bool b) { m_bDenySetQuitMsg = b; }
|
|
void CUser::SetDenySetCTCPReplies(bool b) { m_bDenySetCTCPReplies = b; }
|
|
void CUser::SetDefaultChanModes(const CString& s) { m_sDefaultChanModes = s; }
|
|
void CUser::SetClientEncoding(const CString& s) {
|
|
m_sClientEncoding = CZNC::Get().FixupEncoding(s);
|
|
for (CClient* pClient : GetAllClients()) {
|
|
pClient->SetEncoding(m_sClientEncoding);
|
|
}
|
|
}
|
|
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) {
|
|
return SetChanBufferSize(u, bForce);
|
|
}
|
|
|
|
bool CUser::SetChanBufferSize(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_uChanBufferSize = u;
|
|
return true;
|
|
}
|
|
|
|
bool CUser::SetQueryBufferSize(unsigned int u, bool bForce) {
|
|
if (!bForce && u > CZNC::Get().GetMaxBufferSize()) return false;
|
|
for (CIRCNetwork* pNetwork : m_vIRCNetworks) {
|
|
for (CQuery* pQuery : pNetwork->GetQueries()) {
|
|
pQuery->SetBufferCount(u, bForce);
|
|
}
|
|
}
|
|
m_uQueryBufferSize = 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.AsUpper()) > 0;
|
|
}
|
|
|
|
bool CUser::SetStatusPrefix(const CString& s) {
|
|
if ((!s.empty()) && (s.length() < 6) && (!s.Contains(" "))) {
|
|
m_sStatusPrefix = (s.empty()) ? "*" : s;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CUser::SetLanguage(const CString& s) {
|
|
// They look like ru-RU
|
|
for (char c : s) {
|
|
if (isalpha(c) || c == '-' || c == '_') {
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
m_sLanguage = s;
|
|
// 1.7.0 accidentally used _ instead of -, which made language
|
|
// non-selectable. But it's possible that someone put _ to znc.conf
|
|
// manually.
|
|
// TODO: cleanup _ some time later.
|
|
m_sLanguage.Replace("_", "-");
|
|
return true;
|
|
}
|
|
// !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;
|
|
}
|
|
|
|
/// @deprecated
|
|
const CString& CUser::GetUserName() const { return m_sUsername; }
|
|
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;
|
|
}
|
|
CString CUser::GetRealName() const {
|
|
// Not include version number via GetTag() because of
|
|
// https://github.com/znc/znc/issues/818#issuecomment-70402820
|
|
return (!m_sRealName.Trim_n().empty()) ? m_sRealName
|
|
: "ZNC - https://znc.in";
|
|
}
|
|
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::DenySetIdent() const { return m_bDenySetIdent; }
|
|
bool CUser::DenySetNetwork() const { return m_bDenySetNetwork; }
|
|
bool CUser::DenySetRealName() const { return m_bDenySetRealName; }
|
|
bool CUser::DenySetQuitMsg() const { return m_bDenySetQuitMsg; }
|
|
bool CUser::DenySetCTCPReplies() const { return m_bDenySetCTCPReplies; }
|
|
bool CUser::MultiClients() const { return m_bMultiClients; }
|
|
bool CUser::AuthOnlyViaModule() const { return m_bAuthOnlyViaModule; }
|
|
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 : "%znc%";
|
|
}
|
|
const MCString& CUser::GetCTCPReplies() const { return m_mssCTCPReplies; }
|
|
unsigned int CUser::GetBufferCount() const { return GetChanBufferSize(); }
|
|
unsigned int CUser::GetChanBufferSize() const { return m_uChanBufferSize; }
|
|
unsigned int CUser::GetQueryBufferSize() const { return m_uQueryBufferSize; }
|
|
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; }
|
|
CString CUser::GetLanguage() const { return m_sLanguage; }
|
|
const CString& CUser::GetUserPath() const {
|
|
if (!CFile::Exists(m_sUserPath)) {
|
|
CDir::MakeDir(m_sUserPath);
|
|
}
|
|
return m_sUserPath;
|
|
}
|
|
// !Getters
|
|
|
|
unsigned long long CUser::BytesRead() const {
|
|
unsigned long long uBytes = m_uBytesRead;
|
|
for (const CIRCNetwork* pNetwork : m_vIRCNetworks) {
|
|
uBytes += pNetwork->BytesRead();
|
|
}
|
|
return uBytes;
|
|
}
|
|
|
|
unsigned long long CUser::BytesWritten() const {
|
|
unsigned long long uBytes = m_uBytesWritten;
|
|
for (const CIRCNetwork* pNetwork : m_vIRCNetworks) {
|
|
uBytes += pNetwork->BytesWritten();
|
|
}
|
|
return uBytes;
|
|
}
|