Files
znc/modules/webadmin.cpp
Alexey Sokolov f885699d1a Make list of languages installed discoverable at runtime.
Stop hardcoding Russian in webadmin.
Limit the setting in controlpanel to the known languages, because
untrusted language code might lead to some interesting vulnerabilities.
2017-08-19 18:09:12 +01:00

2100 lines
78 KiB
C++

/*
* Copyright (C) 2004-2017 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/Chan.h>
#include <znc/Server.h>
#include <znc/User.h>
#include <znc/IRCNetwork.h>
#include <znc/IRCSock.h>
using std::stringstream;
using std::make_pair;
using std::set;
using std::vector;
using std::map;
/* Stuff to be able to write this:
// i will be name of local variable, see below
// pUser can be nullptr if only global modules are needed
FOR_EACH_MODULE(i, pUser) {
// i is local variable of type CModules::iterator,
// so *i has type CModule*
}
*/
struct FOR_EACH_MODULE_Type {
enum {
AtGlobal,
AtUser,
AtNetwork,
} where;
CModules CMtemp;
CModules& CMuser;
CModules& CMnet;
FOR_EACH_MODULE_Type(CUser* pUser)
: CMuser(pUser ? pUser->GetModules() : CMtemp), CMnet(CMtemp) {
where = AtGlobal;
}
FOR_EACH_MODULE_Type(CIRCNetwork* pNetwork)
: CMuser(pNetwork ? pNetwork->GetUser()->GetModules() : CMtemp),
CMnet(pNetwork ? pNetwork->GetModules() : CMtemp) {
where = AtGlobal;
}
FOR_EACH_MODULE_Type(std::pair<CUser*, CIRCNetwork*> arg)
: CMuser(arg.first ? arg.first->GetModules() : CMtemp),
CMnet(arg.second ? arg.second->GetModules() : CMtemp) {
where = AtGlobal;
}
operator bool() { return false; }
};
inline bool FOR_EACH_MODULE_CanContinue(FOR_EACH_MODULE_Type& state,
CModules::iterator& i) {
if (state.where == FOR_EACH_MODULE_Type::AtGlobal &&
i == CZNC::Get().GetModules().end()) {
i = state.CMuser.begin();
state.where = FOR_EACH_MODULE_Type::AtUser;
}
if (state.where == FOR_EACH_MODULE_Type::AtUser &&
i == state.CMuser.end()) {
i = state.CMnet.begin();
state.where = FOR_EACH_MODULE_Type::AtNetwork;
}
return !(state.where == FOR_EACH_MODULE_Type::AtNetwork &&
i == state.CMnet.end());
}
#define FOR_EACH_MODULE(I, pUserOrNetwork) \
if (FOR_EACH_MODULE_Type FOR_EACH_MODULE_Var = pUserOrNetwork) { \
} else \
for (CModules::iterator I = CZNC::Get().GetModules().begin(); \
FOR_EACH_MODULE_CanContinue(FOR_EACH_MODULE_Var, I); ++I)
class CWebAdminMod : public CModule {
public:
MODCONSTRUCTOR(CWebAdminMod) {
VPair vParams;
vParams.push_back(make_pair("user", ""));
AddSubPage(std::make_shared<CWebSubPage>(
"settings", t_d("Global Settings"), vParams, CWebSubPage::F_ADMIN));
AddSubPage(std::make_shared<CWebSubPage>(
"edituser", t_d("Your Settings"), vParams));
AddSubPage(std::make_shared<CWebSubPage>("traffic", t_d("Traffic Info"),
vParams));
AddSubPage(std::make_shared<CWebSubPage>(
"listusers", t_d("Manage Users"), vParams, CWebSubPage::F_ADMIN));
}
~CWebAdminMod() override {}
bool OnLoad(const CString& sArgStr, CString& sMessage) override {
if (sArgStr.empty() || CModInfo::GlobalModule != GetType()) return true;
// We don't accept any arguments, but for backwards
// compatibility we have to do some magic here.
sMessage = "Arguments converted to new syntax";
bool bSSL = false;
bool bIPv6 = false;
bool bShareIRCPorts = true;
unsigned short uPort = 8080;
CString sArgs(sArgStr);
CString sPort;
CString sListenHost;
CString sURIPrefix;
while (sArgs.Left(1) == "-") {
CString sOpt = sArgs.Token(0);
sArgs = sArgs.Token(1, true);
if (sOpt.Equals("-IPV6")) {
bIPv6 = true;
} else if (sOpt.Equals("-IPV4")) {
bIPv6 = false;
} else if (sOpt.Equals("-noircport")) {
bShareIRCPorts = false;
} else {
// Uhm... Unknown option? Let's just ignore all
// arguments, older versions would have returned
// an error and denied loading
return true;
}
}
// No arguments left: Only port sharing
if (sArgs.empty() && bShareIRCPorts) return true;
if (sArgs.find(" ") != CString::npos) {
sListenHost = sArgs.Token(0);
sPort = sArgs.Token(1, true);
} else {
sPort = sArgs;
}
if (sPort.Left(1) == "+") {
sPort.TrimLeft("+");
bSSL = true;
}
if (!sPort.empty()) {
uPort = sPort.ToUShort();
}
if (!bShareIRCPorts) {
// Make all existing listeners IRC-only
const vector<CListener*>& vListeners = CZNC::Get().GetListeners();
for (CListener* pListener : vListeners) {
pListener->SetAcceptType(CListener::ACCEPT_IRC);
}
}
// Now turn that into a listener instance
CListener* pListener = new CListener(
uPort, sListenHost, sURIPrefix, bSSL,
(!bIPv6 ? ADDR_IPV4ONLY : ADDR_ALL), CListener::ACCEPT_HTTP);
if (!pListener->Listen()) {
sMessage = "Failed to add backwards-compatible listener";
return false;
}
CZNC::Get().AddListener(pListener);
SetArgs("");
return true;
}
CUser* GetNewUser(CWebSock& WebSock, CUser* pUser) {
std::shared_ptr<CWebSession> spSession = WebSock.GetSession();
CString sUsername = WebSock.GetParam("newuser");
if (sUsername.empty()) {
sUsername = WebSock.GetParam("user");
}
if (sUsername.empty()) {
WebSock.PrintErrorPage(
t_s("Invalid Submission [Username is required]"));
return nullptr;
}
if (pUser) {
/* If we are editing a user we must not change the user name */
sUsername = pUser->GetUserName();
}
CString sArg = WebSock.GetParam("password");
if (sArg != WebSock.GetParam("password2")) {
WebSock.PrintErrorPage(
t_s("Invalid Submission [Passwords do not match]"));
return nullptr;
}
CUser* pNewUser = new CUser(sUsername);
if (!sArg.empty()) {
CString sSalt = CUtils::GetSalt();
CString sHash = CUser::SaltedHash(sArg, sSalt);
pNewUser->SetPass(sHash, CUser::HASH_DEFAULT, sSalt);
}
VCString vsArgs;
WebSock.GetRawParam("allowedips").Split("\n", vsArgs);
if (vsArgs.size()) {
for (const CString& sHost : vsArgs) {
pNewUser->AddAllowedHost(sHost.Trim_n());
}
} else {
pNewUser->AddAllowedHost("*");
}
WebSock.GetRawParam("ctcpreplies").Split("\n", vsArgs);
for (const CString& sReply : vsArgs) {
pNewUser->AddCTCPReply(sReply.Token(0).Trim_n(),
sReply.Token(1, true).Trim_n());
}
sArg = WebSock.GetParam("nick");
if (!sArg.empty()) {
pNewUser->SetNick(sArg);
}
sArg = WebSock.GetParam("altnick");
if (!sArg.empty()) {
pNewUser->SetAltNick(sArg);
}
sArg = WebSock.GetParam("statusprefix");
if (!sArg.empty()) {
pNewUser->SetStatusPrefix(sArg);
}
sArg = WebSock.GetParam("ident");
if (!sArg.empty()) {
pNewUser->SetIdent(sArg);
}
sArg = WebSock.GetParam("realname");
if (!sArg.empty()) {
pNewUser->SetRealName(sArg);
}
sArg = WebSock.GetParam("quitmsg");
if (!sArg.empty()) {
pNewUser->SetQuitMsg(sArg);
}
sArg = WebSock.GetParam("chanmodes");
if (!sArg.empty()) {
pNewUser->SetDefaultChanModes(sArg);
}
sArg = WebSock.GetParam("timestampformat");
if (!sArg.empty()) {
pNewUser->SetTimestampFormat(sArg);
}
sArg = WebSock.GetParam("bindhost");
// To change BindHosts be admin or don't have DenySetBindHost
if (spSession->IsAdmin() || !spSession->GetUser()->DenySetBindHost()) {
CString sArg2 = WebSock.GetParam("dccbindhost");
if (!sArg.empty()) {
pNewUser->SetBindHost(sArg);
}
if (!sArg2.empty()) {
pNewUser->SetDCCBindHost(sArg2);
}
} else if (pUser) {
pNewUser->SetBindHost(pUser->GetBindHost());
pNewUser->SetDCCBindHost(pUser->GetDCCBindHost());
}
sArg = WebSock.GetParam("chanbufsize");
if (!sArg.empty()) {
// First apply the old limit in case the new one is too high
if (pUser)
pNewUser->SetChanBufferSize(pUser->GetChanBufferSize(), true);
pNewUser->SetChanBufferSize(sArg.ToUInt(), spSession->IsAdmin());
}
sArg = WebSock.GetParam("querybufsize");
if (!sArg.empty()) {
// First apply the old limit in case the new one is too high
if (pUser)
pNewUser->SetQueryBufferSize(pUser->GetQueryBufferSize(), true);
pNewUser->SetQueryBufferSize(sArg.ToUInt(), spSession->IsAdmin());
}
pNewUser->SetSkinName(WebSock.GetParam("skin"));
pNewUser->SetAutoClearChanBuffer(
WebSock.GetParam("autoclearchanbuffer").ToBool());
pNewUser->SetMultiClients(WebSock.GetParam("multiclients").ToBool());
pNewUser->SetTimestampAppend(
WebSock.GetParam("appendtimestamp").ToBool());
pNewUser->SetTimestampPrepend(
WebSock.GetParam("prependtimestamp").ToBool());
pNewUser->SetTimezone(WebSock.GetParam("timezone"));
pNewUser->SetJoinTries(WebSock.GetParam("jointries").ToUInt());
pNewUser->SetMaxJoins(WebSock.GetParam("maxjoins").ToUInt());
pNewUser->SetAutoClearQueryBuffer(
WebSock.GetParam("autoclearquerybuffer").ToBool());
pNewUser->SetMaxQueryBuffers(
WebSock.GetParam("maxquerybuffers").ToUInt());
unsigned int uNoTrafficTimeout =
WebSock.GetParam("notraffictimeout").ToUInt();
if (uNoTrafficTimeout < 30) {
uNoTrafficTimeout = 30;
WebSock.GetSession()->AddError(
t_s("Timeout can't be less than 30 seconds!"));
}
pNewUser->SetNoTrafficTimeout(uNoTrafficTimeout);
#ifdef HAVE_I18N
CString sLanguage = WebSock.GetParam("language");
if (CTranslationInfo::GetTranslations().count(sLanguage) == 0) {
sLanguage = "";
}
pNewUser->SetLanguage(sLanguage);
#endif
#ifdef HAVE_ICU
CString sEncodingUtf = WebSock.GetParam("encoding_utf");
if (sEncodingUtf == "legacy") {
pNewUser->SetClientEncoding("");
}
CString sEncoding = WebSock.GetParam("encoding");
if (sEncoding.empty()) {
sEncoding = "UTF-8";
}
if (sEncodingUtf == "send") {
pNewUser->SetClientEncoding("^" + sEncoding);
} else if (sEncodingUtf == "receive") {
pNewUser->SetClientEncoding("*" + sEncoding);
} else if (sEncodingUtf == "simple") {
pNewUser->SetClientEncoding(sEncoding);
}
#endif
if (spSession->IsAdmin()) {
pNewUser->SetDenyLoadMod(WebSock.GetParam("denyloadmod").ToBool());
pNewUser->SetDenySetBindHost(
WebSock.GetParam("denysetbindhost").ToBool());
sArg = WebSock.GetParam("maxnetworks");
if (!sArg.empty()) pNewUser->SetMaxNetworks(sArg.ToUInt());
} else if (pUser) {
pNewUser->SetDenyLoadMod(pUser->DenyLoadMod());
pNewUser->SetDenySetBindHost(pUser->DenySetBindHost());
pNewUser->SetMaxNetworks(pUser->MaxNetworks());
}
// If pUser is not nullptr, we are editing an existing user.
// Users must not be able to change their own admin flag.
if (pUser != CZNC::Get().FindUser(WebSock.GetUser())) {
pNewUser->SetAdmin(WebSock.GetParam("isadmin").ToBool());
} else if (pUser) {
pNewUser->SetAdmin(pUser->IsAdmin());
}
if (spSession->IsAdmin() || (pUser && !pUser->DenyLoadMod())) {
WebSock.GetParamValues("loadmod", vsArgs);
// disallow unload webadmin from itself
if (CModInfo::UserModule == GetType() &&
pUser == CZNC::Get().FindUser(WebSock.GetUser())) {
bool bLoadedWebadmin = false;
for (const CString& s : vsArgs) {
CString sModName = s.TrimRight_n("\r");
if (sModName == GetModName()) {
bLoadedWebadmin = true;
break;
}
}
if (!bLoadedWebadmin) {
vsArgs.push_back(GetModName());
}
}
for (const CString& s : vsArgs) {
CString sModRet;
CString sModName = s.TrimRight_n("\r");
CString sModLoadError;
if (!sModName.empty()) {
CString sArgs = WebSock.GetParam("modargs_" + sModName);
try {
if (!pNewUser->GetModules().LoadModule(
sModName, sArgs, CModInfo::UserModule, pNewUser,
nullptr, sModRet)) {
sModLoadError =
t_f("Unable to load module [{1}]: {2}")(
sModName, sModRet);
}
} catch (...) {
sModLoadError = t_f(
"Unable to load module [{1}] with arguments [{2}]")(
sModName, sArgs);
}
if (!sModLoadError.empty()) {
DEBUG(sModLoadError);
spSession->AddError(sModLoadError);
}
}
}
} else if (pUser) {
CModules& Modules = pUser->GetModules();
for (const CModule* pMod : Modules) {
CString sModName = pMod->GetModName();
CString sArgs = pMod->GetArgs();
CString sModRet;
CString sModLoadError;
try {
if (!pNewUser->GetModules().LoadModule(
sModName, sArgs, CModInfo::UserModule, pNewUser,
nullptr, sModRet)) {
sModLoadError = t_f("Unable to load module [{1}]: {2}")(
sModName, sModRet);
}
} catch (...) {
sModLoadError =
t_f("Unable to load module [{1}] with arguments [{2}]")(
sModName, sArgs);
}
if (!sModLoadError.empty()) {
DEBUG(sModLoadError);
spSession->AddError(sModLoadError);
}
}
}
return pNewUser;
}
CString SafeGetUserNameParam(CWebSock& WebSock) {
CString sUserName = WebSock.GetParam("user"); // check for POST param
if (sUserName.empty() && !WebSock.IsPost()) {
// if no POST param named user has been given and we are not
// saving this form, fall back to using the GET parameter.
sUserName = WebSock.GetParam("user", false);
}
return sUserName;
}
CString SafeGetNetworkParam(CWebSock& WebSock) {
CString sNetwork = WebSock.GetParam("network"); // check for POST param
if (sNetwork.empty() && !WebSock.IsPost()) {
// if no POST param named user has been given and we are not
// saving this form, fall back to using the GET parameter.
sNetwork = WebSock.GetParam("network", false);
}
return sNetwork;
}
CUser* SafeGetUserFromParam(CWebSock& WebSock) {
return CZNC::Get().FindUser(SafeGetUserNameParam(WebSock));
}
CIRCNetwork* SafeGetNetworkFromParam(CWebSock& WebSock) {
CUser* pUser = CZNC::Get().FindUser(SafeGetUserNameParam(WebSock));
CIRCNetwork* pNetwork = nullptr;
if (pUser) {
pNetwork = pUser->FindNetwork(SafeGetNetworkParam(WebSock));
}
return pNetwork;
}
CString GetWebMenuTitle() override { return "webadmin"; }
bool OnWebRequest(CWebSock& WebSock, const CString& sPageName,
CTemplate& Tmpl) override {
std::shared_ptr<CWebSession> spSession = WebSock.GetSession();
if (sPageName == "settings") {
// Admin Check
if (!spSession->IsAdmin()) {
return false;
}
return SettingsPage(WebSock, Tmpl);
} else if (sPageName == "adduser") {
// Admin Check
if (!spSession->IsAdmin()) {
return false;
}
return UserPage(WebSock, Tmpl);
} else if (sPageName == "addnetwork") {
CUser* pUser = SafeGetUserFromParam(WebSock);
// Admin||Self Check
if (!spSession->IsAdmin() &&
(!spSession->GetUser() || spSession->GetUser() != pUser)) {
return false;
}
if (pUser) {
return NetworkPage(WebSock, Tmpl, pUser);
}
WebSock.PrintErrorPage(t_s("No such user"));
return true;
} else if (sPageName == "editnetwork") {
CIRCNetwork* pNetwork = SafeGetNetworkFromParam(WebSock);
// Admin||Self Check
if (!spSession->IsAdmin() &&
(!spSession->GetUser() || !pNetwork ||
spSession->GetUser() != pNetwork->GetUser())) {
return false;
}
if (!pNetwork) {
WebSock.PrintErrorPage(t_s("No such user or network"));
return true;
}
return NetworkPage(WebSock, Tmpl, pNetwork->GetUser(), pNetwork);
} else if (sPageName == "delnetwork") {
CString sUser = WebSock.GetParam("user");
if (sUser.empty() && !WebSock.IsPost()) {
sUser = WebSock.GetParam("user", false);
}
CUser* pUser = CZNC::Get().FindUser(sUser);
// Admin||Self Check
if (!spSession->IsAdmin() &&
(!spSession->GetUser() || spSession->GetUser() != pUser)) {
return false;
}
return DelNetwork(WebSock, pUser, Tmpl);
} else if (sPageName == "editchan") {
CIRCNetwork* pNetwork = SafeGetNetworkFromParam(WebSock);
// Admin||Self Check
if (!spSession->IsAdmin() &&
(!spSession->GetUser() || !pNetwork ||
spSession->GetUser() != pNetwork->GetUser())) {
return false;
}
if (!pNetwork) {
WebSock.PrintErrorPage(t_s("No such user or network"));
return true;
}
CString sChan = WebSock.GetParam("name");
if (sChan.empty() && !WebSock.IsPost()) {
sChan = WebSock.GetParam("name", false);
}
CChan* pChan = pNetwork->FindChan(sChan);
if (!pChan) {
WebSock.PrintErrorPage(t_s("No such channel"));
return true;
}
return ChanPage(WebSock, Tmpl, pNetwork, pChan);
} else if (sPageName == "addchan") {
CIRCNetwork* pNetwork = SafeGetNetworkFromParam(WebSock);
// Admin||Self Check
if (!spSession->IsAdmin() &&
(!spSession->GetUser() || !pNetwork ||
spSession->GetUser() != pNetwork->GetUser())) {
return false;
}
if (pNetwork) {
return ChanPage(WebSock, Tmpl, pNetwork);
}
WebSock.PrintErrorPage(t_s("No such user or network"));
return true;
} else if (sPageName == "delchan") {
CIRCNetwork* pNetwork = SafeGetNetworkFromParam(WebSock);
// Admin||Self Check
if (!spSession->IsAdmin() &&
(!spSession->GetUser() || !pNetwork ||
spSession->GetUser() != pNetwork->GetUser())) {
return false;
}
if (pNetwork) {
return DelChan(WebSock, pNetwork);
}
WebSock.PrintErrorPage(t_s("No such user or network"));
return true;
} else if (sPageName == "deluser") {
if (!spSession->IsAdmin()) {
return false;
}
if (!WebSock.IsPost()) {
// Show the "Are you sure?" page:
CString sUser = WebSock.GetParam("user", false);
CUser* pUser = CZNC::Get().FindUser(sUser);
if (!pUser) {
WebSock.PrintErrorPage(t_s("No such user"));
return true;
}
Tmpl.SetFile("del_user.tmpl");
Tmpl["Username"] = sUser;
return true;
}
// The "Are you sure?" page has been submitted with "Yes",
// so we actually delete the user now:
CString sUser = WebSock.GetParam("user");
CUser* pUser = CZNC::Get().FindUser(sUser);
if (pUser && pUser == spSession->GetUser()) {
WebSock.PrintErrorPage(
t_s("Please don't delete yourself, suicide is not the "
"answer!"));
return true;
} else if (CZNC::Get().DeleteUser(sUser)) {
WebSock.Redirect(GetWebPath() + "listusers");
return true;
}
WebSock.PrintErrorPage(t_s("No such user"));
return true;
} else if (sPageName == "edituser") {
CString sUserName = SafeGetUserNameParam(WebSock);
CUser* pUser = CZNC::Get().FindUser(sUserName);
if (!pUser) {
if (sUserName.empty()) {
pUser = spSession->GetUser();
} // else: the "no such user" message will be printed.
}
// Admin||Self Check
if (!spSession->IsAdmin() &&
(!spSession->GetUser() || spSession->GetUser() != pUser)) {
return false;
}
if (pUser) {
return UserPage(WebSock, Tmpl, pUser);
}
WebSock.PrintErrorPage(t_s("No such user"));
return true;
} else if (sPageName == "listusers" && spSession->IsAdmin()) {
return ListUsersPage(WebSock, Tmpl);
} else if (sPageName == "traffic") {
return TrafficPage(WebSock, Tmpl);
} else if (sPageName == "index") {
return true;
} else if (sPageName == "add_listener") {
// Admin Check
if (!spSession->IsAdmin()) {
return false;
}
return AddListener(WebSock, Tmpl);
} else if (sPageName == "del_listener") {
// Admin Check
if (!spSession->IsAdmin()) {
return false;
}
return DelListener(WebSock, Tmpl);
}
return false;
}
bool ChanPage(CWebSock& WebSock, CTemplate& Tmpl, CIRCNetwork* pNetwork,
CChan* pChan = nullptr) {
std::shared_ptr<CWebSession> spSession = WebSock.GetSession();
Tmpl.SetFile("add_edit_chan.tmpl");
CUser* pUser = pNetwork->GetUser();
if (!pUser) {
WebSock.PrintErrorPage(t_s("No such user"));
return true;
}
if (!WebSock.GetParam("submitted").ToUInt()) {
Tmpl["User"] = pUser->GetUserName();
Tmpl["Network"] = pNetwork->GetName();
CTemplate& breadUser = Tmpl.AddRow("BreadCrumbs");
breadUser["Text"] = t_f("Edit User [{1}]")(pUser->GetUserName());
breadUser["URL"] =
GetWebPath() + "edituser?user=" + pUser->GetUserName();
CTemplate& breadNet = Tmpl.AddRow("BreadCrumbs");
breadNet["Text"] = t_f("Edit Network [{1}]")(pNetwork->GetName());
breadNet["URL"] = GetWebPath() + "editnetwork?user=" +
pUser->GetUserName() + "&network=" +
pNetwork->GetName();
CTemplate& breadChan = Tmpl.AddRow("BreadCrumbs");
if (pChan) {
Tmpl["Action"] = "editchan";
Tmpl["Edit"] = "true";
Tmpl["Title"] =
t_f("Edit Channel [{1}] of Network [{2}] of User [{3}]")(
pChan->GetName(), pNetwork->GetName(),
pUser->GetUserName());
Tmpl["ChanName"] = pChan->GetName();
Tmpl["BufferSize"] = CString(pChan->GetBufferCount());
Tmpl["DefModes"] = pChan->GetDefaultModes();
Tmpl["Key"] = pChan->GetKey();
breadChan["Text"] = t_f("Edit Channel [{1}]")(pChan->GetName());
if (pChan->InConfig()) {
Tmpl["InConfig"] = "true";
}
} else {
Tmpl["Action"] = "addchan";
Tmpl["Title"] =
t_f("Add Channel to Network [{1}] of User [{2}]")(
pNetwork->GetName(), pUser->GetUserName());
Tmpl["BufferSize"] = CString(pUser->GetBufferCount());
Tmpl["DefModes"] = CString(pUser->GetDefaultChanModes());
Tmpl["InConfig"] = "true";
breadChan["Text"] = t_s("Add Channel");
}
// o1 used to be AutoCycle which was removed
CTemplate& o2 = Tmpl.AddRow("OptionLoop");
o2["Name"] = "autoclearchanbuffer";
o2["DisplayName"] = t_s("Auto Clear Chan Buffer");
o2["Tooltip"] =
t_s("Automatically Clear Channel Buffer After Playback");
if ((pChan && pChan->AutoClearChanBuffer()) ||
(!pChan && pUser->AutoClearChanBuffer())) {
o2["Checked"] = "true";
}
CTemplate& o3 = Tmpl.AddRow("OptionLoop");
o3["Name"] = "detached";
o3["DisplayName"] = t_s("Detached");
if (pChan && pChan->IsDetached()) {
o3["Checked"] = "true";
}
CTemplate& o4 = Tmpl.AddRow("OptionLoop");
o4["Name"] = "enabled";
o4["DisplayName"] = t_s("Enabled");
if (pChan && !pChan->IsDisabled()) {
o4["Checked"] = "true";
}
FOR_EACH_MODULE(i, pNetwork) {
CTemplate& mod = Tmpl.AddRow("EmbeddedModuleLoop");
mod.insert(Tmpl.begin(), Tmpl.end());
mod["WebadminAction"] = "display";
if ((*i)->OnEmbeddedWebRequest(WebSock, "webadmin/channel",
mod)) {
mod["Embed"] = WebSock.FindTmpl(*i, "WebadminChan.tmpl");
mod["ModName"] = (*i)->GetModName();
}
}
return true;
}
CString sChanName = WebSock.GetParam("name").Trim_n();
if (!pChan) {
if (sChanName.empty()) {
WebSock.PrintErrorPage(
t_s("Channel name is a required argument"));
return true;
}
// This could change the channel name and e.g. add a "#" prefix
pChan = new CChan(sChanName, pNetwork, true);
if (pNetwork->FindChan(pChan->GetName())) {
WebSock.PrintErrorPage(
t_f("Channel [{1}] already exists")(pChan->GetName()));
delete pChan;
return true;
}
if (!pNetwork->AddChan(pChan)) {
WebSock.PrintErrorPage(
t_f("Could not add channel [{1}]")(sChanName));
return true;
}
}
if (WebSock.GetParam("buffersize").empty()) {
pChan->ResetBufferCount();
} else {
unsigned int uBufferSize = WebSock.GetParam("buffersize").ToUInt();
if (pChan->GetBufferCount() != uBufferSize) {
pChan->SetBufferCount(uBufferSize, spSession->IsAdmin());
}
}
pChan->SetDefaultModes(WebSock.GetParam("defmodes"));
pChan->SetInConfig(WebSock.GetParam("save").ToBool());
bool bAutoClearChanBuffer =
WebSock.GetParam("autoclearchanbuffer").ToBool();
if (pChan->AutoClearChanBuffer() != bAutoClearChanBuffer) {
pChan->SetAutoClearChanBuffer(
WebSock.GetParam("autoclearchanbuffer").ToBool());
}
pChan->SetKey(WebSock.GetParam("key"));
bool bDetached = WebSock.GetParam("detached").ToBool();
if (pChan->IsDetached() != bDetached) {
if (bDetached) {
pChan->DetachUser();
} else {
pChan->AttachUser();
}
}
bool bEnabled = WebSock.GetParam("enabled").ToBool();
if (bEnabled)
pChan->Enable();
else
pChan->Disable();
CTemplate TmplMod;
TmplMod["User"] = pUser->GetUserName();
TmplMod["ChanName"] = pChan->GetName();
TmplMod["WebadminAction"] = "change";
FOR_EACH_MODULE(it, pNetwork) {
(*it)->OnEmbeddedWebRequest(WebSock, "webadmin/channel", TmplMod);
}
if (!CZNC::Get().WriteConfig()) {
WebSock.PrintErrorPage(t_s(
"Channel was added/modified, but config file was not written"));
return true;
}
if (WebSock.HasParam("submit_return")) {
WebSock.Redirect(GetWebPath() + "editnetwork?user=" +
pUser->GetUserName().Escape_n(CString::EURL) +
"&network=" +
pNetwork->GetName().Escape_n(CString::EURL));
} else {
WebSock.Redirect(
GetWebPath() + "editchan?user=" +
pUser->GetUserName().Escape_n(CString::EURL) + "&network=" +
pNetwork->GetName().Escape_n(CString::EURL) + "&name=" +
pChan->GetName().Escape_n(CString::EURL));
}
return true;
}
bool NetworkPage(CWebSock& WebSock, CTemplate& Tmpl, CUser* pUser,
CIRCNetwork* pNetwork = nullptr) {
std::shared_ptr<CWebSession> spSession = WebSock.GetSession();
Tmpl.SetFile("add_edit_network.tmpl");
if (!WebSock.GetParam("submitted").ToUInt()) {
Tmpl["Username"] = pUser->GetUserName();
CTemplate& breadNet = Tmpl.AddRow("BreadCrumbs");
CIRCNetwork EmptyNetwork(pUser, "");
CIRCNetwork* pRealNetwork = pNetwork;
if (pNetwork) {
Tmpl["Action"] = "editnetwork";
Tmpl["Edit"] = "true";
Tmpl["Title"] = t_f("Edit Network [{1}] of User [{2}]")(
pNetwork->GetName(), pUser->GetUserName());
breadNet["Text"] =
t_f("Edit Network [{1}]")(pNetwork->GetName());
} else {
if (!spSession->IsAdmin() && !pUser->HasSpaceForNewNetwork()) {
WebSock.PrintErrorPage(
t_s("Network number limit reached. Ask an admin to "
"increase the limit for you, or delete unneeded "
"networks from Your Settings."));
return true;
}
Tmpl["Action"] = "addnetwork";
Tmpl["Title"] =
t_f("Add Network for User [{1}]")(pUser->GetUserName());
breadNet["Text"] = t_s("Add Network");
pNetwork = &EmptyNetwork;
}
set<CModInfo> ssNetworkMods;
CZNC::Get().GetModules().GetAvailableMods(ssNetworkMods,
CModInfo::NetworkModule);
set<CModInfo> ssDefaultMods;
CZNC::Get().GetModules().GetDefaultMods(ssDefaultMods,
CModInfo::NetworkModule);
for (const CModInfo& Info : ssNetworkMods) {
CTemplate& l = Tmpl.AddRow("ModuleLoop");
l["Name"] = Info.GetName();
l["Description"] = Info.GetDescription();
l["Wiki"] = Info.GetWikiPage();
l["HasArgs"] = CString(Info.GetHasArgs());
l["ArgsHelpText"] = Info.GetArgsHelpText();
if (pRealNetwork) {
CModule* pModule =
pNetwork->GetModules().FindModule(Info.GetName());
if (pModule) {
l["Checked"] = "true";
l["Args"] = pModule->GetArgs();
}
} else {
for (const CModInfo& DInfo : ssDefaultMods) {
if (Info.GetName() == DInfo.GetName()) {
l["Checked"] = "true";
}
}
}
// Check if module is loaded globally
l["CanBeLoadedGlobally"] =
CString(Info.SupportsType(CModInfo::GlobalModule));
l["LoadedGlobally"] =
CString(CZNC::Get().GetModules().FindModule(
Info.GetName()) != nullptr);
// Check if module is loaded by user
l["CanBeLoadedByUser"] =
CString(Info.SupportsType(CModInfo::UserModule));
l["LoadedByUser"] = CString(
pUser->GetModules().FindModule(Info.GetName()) != nullptr);
if (!spSession->IsAdmin() && pUser->DenyLoadMod()) {
l["Disabled"] = "true";
}
}
// To change BindHosts be admin or don't have DenySetBindHost
if (spSession->IsAdmin() ||
!spSession->GetUser()->DenySetBindHost()) {
Tmpl["BindHostEdit"] = "true";
Tmpl["BindHost"] = pNetwork->GetBindHost();
}
CTemplate& breadUser = Tmpl.AddRow("BreadCrumbs");
breadUser["Text"] = t_f("Edit User [{1}]")(pUser->GetUserName());
breadUser["URL"] =
GetWebPath() + "edituser?user=" + pUser->GetUserName();
Tmpl["Name"] = pNetwork->GetName();
Tmpl["Nick"] = pNetwork->GetNick();
Tmpl["AltNick"] = pNetwork->GetAltNick();
Tmpl["Ident"] = pNetwork->GetIdent();
Tmpl["RealName"] = pNetwork->GetRealName();
Tmpl["QuitMsg"] = pNetwork->GetQuitMsg();
Tmpl["FloodProtection"] =
CString(CIRCSock::IsFloodProtected(pNetwork->GetFloodRate()));
Tmpl["FloodRate"] = CString(pNetwork->GetFloodRate());
Tmpl["FloodBurst"] = CString(pNetwork->GetFloodBurst());
Tmpl["JoinDelay"] = CString(pNetwork->GetJoinDelay());
Tmpl["IRCConnectEnabled"] =
CString(pNetwork->GetIRCConnectEnabled());
Tmpl["TrustAllCerts"] = CString(pNetwork->GetTrustAllCerts());
Tmpl["TrustPKI"] = CString(pNetwork->GetTrustPKI());
const vector<CServer*>& vServers = pNetwork->GetServers();
for (const CServer* pServer : vServers) {
CTemplate& l = Tmpl.AddRow("ServerLoop");
l["Server"] = pServer->GetString();
}
const vector<CChan*>& Channels = pNetwork->GetChans();
for (const CChan* pChan : Channels) {
CTemplate& l = Tmpl.AddRow("ChannelLoop");
l["Network"] = pNetwork->GetName();
l["Username"] = pUser->GetUserName();
l["Name"] = pChan->GetName();
l["Perms"] = pChan->GetPermStr();
l["CurModes"] = pChan->GetModeString();
l["DefModes"] = pChan->GetDefaultModes();
if (pChan->HasBufferCountSet()) {
l["BufferSize"] = CString(pChan->GetBufferCount());
} else {
l["BufferSize"] =
CString(pChan->GetBufferCount()) + " (default)";
}
l["Options"] = pChan->GetOptions();
if (pChan->InConfig()) {
l["InConfig"] = "true";
}
}
for (const CString& sFP : pNetwork->GetTrustedFingerprints()) {
CTemplate& l = Tmpl.AddRow("TrustedFingerprints");
l["FP"] = sFP;
}
FOR_EACH_MODULE(i, make_pair(pUser, pNetwork)) {
CTemplate& mod = Tmpl.AddRow("EmbeddedModuleLoop");
mod.insert(Tmpl.begin(), Tmpl.end());
mod["WebadminAction"] = "display";
if ((*i)->OnEmbeddedWebRequest(WebSock, "webadmin/network",
mod)) {
mod["Embed"] = WebSock.FindTmpl(*i, "WebadminNetwork.tmpl");
mod["ModName"] = (*i)->GetModName();
}
}
#ifdef HAVE_ICU
for (const CString& sEncoding : CUtils::GetEncodings()) {
CTemplate& l = Tmpl.AddRow("EncodingLoop");
l["Encoding"] = sEncoding;
}
const CString sEncoding =
pRealNetwork ? pNetwork->GetEncoding() : "^UTF-8";
if (sEncoding.empty()) {
Tmpl["EncodingUtf"] = "legacy";
} else if (sEncoding[0] == '*') {
Tmpl["EncodingUtf"] = "receive";
Tmpl["Encoding"] = sEncoding.substr(1);
} else if (sEncoding[0] == '^') {
Tmpl["EncodingUtf"] = "send";
Tmpl["Encoding"] = sEncoding.substr(1);
} else {
Tmpl["EncodingUtf"] = "simple";
Tmpl["Encoding"] = sEncoding;
}
Tmpl["LegacyEncodingDisabled"] =
CString(CZNC::Get().IsForcingEncoding());
#else
Tmpl["LegacyEncodingDisabled"] = "true";
Tmpl["EncodingDisabled"] = "true";
Tmpl["EncodingUtf"] = "legacy";
#endif
return true;
}
CString sName = WebSock.GetParam("name").Trim_n();
if (sName.empty()) {
WebSock.PrintErrorPage(t_s("Network name is a required argument"));
return true;
}
if (!pNetwork && !spSession->IsAdmin() &&
!pUser->HasSpaceForNewNetwork()) {
WebSock.PrintErrorPage(t_s(
"Network number limit reached. Ask an admin to increase the "
"limit for you, or delete unneeded networks from Your "
"Settings."));
return true;
}
if (!pNetwork || pNetwork->GetName() != sName) {
CString sNetworkAddError;
CIRCNetwork* pOldNetwork = pNetwork;
pNetwork = pUser->AddNetwork(sName, sNetworkAddError);
if (!pNetwork) {
WebSock.PrintErrorPage(sNetworkAddError);
return true;
}
if (pOldNetwork) {
for (CModule* pModule : pOldNetwork->GetModules()) {
CString sPath = pUser->GetUserPath() + "/networks/" +
sName + "/moddata/" + pModule->GetModName();
pModule->MoveRegistry(sPath);
}
pNetwork->Clone(*pOldNetwork, false);
pUser->DeleteNetwork(pOldNetwork->GetName());
}
}
CString sArg;
pNetwork->SetNick(WebSock.GetParam("nick"));
pNetwork->SetAltNick(WebSock.GetParam("altnick"));
pNetwork->SetIdent(WebSock.GetParam("ident"));
pNetwork->SetRealName(WebSock.GetParam("realname"));
pNetwork->SetQuitMsg(WebSock.GetParam("quitmsg"));
pNetwork->SetIRCConnectEnabled(WebSock.GetParam("doconnect").ToBool());
pNetwork->SetTrustAllCerts(WebSock.GetParam("trustallcerts").ToBool());
pNetwork->SetTrustPKI(WebSock.GetParam("trustpki").ToBool());
sArg = WebSock.GetParam("bindhost");
// To change BindHosts be admin or don't have DenySetBindHost
if (spSession->IsAdmin() || !spSession->GetUser()->DenySetBindHost()) {
pNetwork->SetBindHost(WebSock.GetParam("bindhost"));
}
if (WebSock.GetParam("floodprotection").ToBool()) {
pNetwork->SetFloodRate(WebSock.GetParam("floodrate").ToDouble());
pNetwork->SetFloodBurst(WebSock.GetParam("floodburst").ToUShort());
} else {
pNetwork->SetFloodRate(-1);
}
pNetwork->SetJoinDelay(WebSock.GetParam("joindelay").ToUShort());
#ifdef HAVE_ICU
CString sEncodingUtf = WebSock.GetParam("encoding_utf");
if (sEncodingUtf == "legacy") {
pNetwork->SetEncoding("");
}
CString sEncoding = WebSock.GetParam("encoding");
if (sEncoding.empty()) {
sEncoding = "UTF-8";
}
if (sEncodingUtf == "send") {
pNetwork->SetEncoding("^" + sEncoding);
} else if (sEncodingUtf == "receive") {
pNetwork->SetEncoding("*" + sEncoding);
} else if (sEncodingUtf == "simple") {
pNetwork->SetEncoding(sEncoding);
}
#endif
VCString vsArgs;
pNetwork->DelServers();
WebSock.GetRawParam("servers").Split("\n", vsArgs);
for (const CString& sServer : vsArgs) {
pNetwork->AddServer(sServer.Trim_n());
}
WebSock.GetRawParam("fingerprints").Split("\n", vsArgs);
pNetwork->ClearTrustedFingerprints();
for (const CString& sFP : vsArgs) {
pNetwork->AddTrustedFingerprint(sFP);
}
WebSock.GetParamValues("channel", vsArgs);
for (const CString& sChan : vsArgs) {
CChan* pChan = pNetwork->FindChan(sChan.TrimRight_n("\r"));
if (pChan) {
pChan->SetInConfig(WebSock.GetParam("save_" + sChan).ToBool());
}
}
set<CString> ssArgs;
WebSock.GetParamValues("loadmod", ssArgs);
if (spSession->IsAdmin() || !pUser->DenyLoadMod()) {
for (const CString& s : ssArgs) {
CString sModRet;
CString sModName = s.TrimRight_n("\r");
CString sModLoadError;
if (!sModName.empty()) {
CString sArgs = WebSock.GetParam("modargs_" + sModName);
CModule* pMod = pNetwork->GetModules().FindModule(sModName);
if (!pMod) {
if (!pNetwork->GetModules().LoadModule(
sModName, sArgs, CModInfo::NetworkModule, pUser,
pNetwork, sModRet)) {
sModLoadError =
t_f("Unable to load module [{1}]: {2}")(
sModName, sModRet);
}
} else if (pMod->GetArgs() != sArgs) {
if (!pNetwork->GetModules().ReloadModule(
sModName, sArgs, pUser, pNetwork, sModRet)) {
sModLoadError =
t_f("Unable to reload module [{1}]: {2}")(
sModName, sModRet);
}
}
if (!sModLoadError.empty()) {
DEBUG(sModLoadError);
WebSock.GetSession()->AddError(sModLoadError);
}
}
}
}
const CModules& vCurMods = pNetwork->GetModules();
set<CString> ssUnloadMods;
for (const CModule* pCurMod : vCurMods) {
if (ssArgs.find(pCurMod->GetModName()) == ssArgs.end() &&
pCurMod->GetModName() != GetModName()) {
ssUnloadMods.insert(pCurMod->GetModName());
}
}
for (const CString& sMod : ssUnloadMods) {
pNetwork->GetModules().UnloadModule(sMod);
}
CTemplate TmplMod;
TmplMod["Username"] = pUser->GetUserName();
TmplMod["Name"] = pNetwork->GetName();
TmplMod["WebadminAction"] = "change";
FOR_EACH_MODULE(it, make_pair(pUser, pNetwork)) {
(*it)->OnEmbeddedWebRequest(WebSock, "webadmin/network", TmplMod);
}
if (!CZNC::Get().WriteConfig()) {
WebSock.PrintErrorPage(t_s(
"Network was added/modified, but config file was not written"));
return true;
}
if (WebSock.HasParam("submit_return")) {
WebSock.Redirect(GetWebPath() + "edituser?user=" +
pUser->GetUserName().Escape_n(CString::EURL));
} else {
WebSock.Redirect(GetWebPath() + "editnetwork?user=" +
pUser->GetUserName().Escape_n(CString::EURL) +
"&network=" +
pNetwork->GetName().Escape_n(CString::EURL));
}
return true;
}
bool DelNetwork(CWebSock& WebSock, CUser* pUser, CTemplate& Tmpl) {
CString sNetwork = WebSock.GetParam("name");
if (sNetwork.empty() && !WebSock.IsPost()) {
sNetwork = WebSock.GetParam("name", false);
}
if (!pUser) {
WebSock.PrintErrorPage(t_s("No such user"));
return true;
}
if (sNetwork.empty()) {
WebSock.PrintErrorPage(
t_s("That network doesn't exist for this user"));
return true;
}
if (!WebSock.IsPost()) {
// Show the "Are you sure?" page:
Tmpl.SetFile("del_network.tmpl");
Tmpl["Username"] = pUser->GetUserName();
Tmpl["Network"] = sNetwork;
return true;
}
pUser->DeleteNetwork(sNetwork);
if (!CZNC::Get().WriteConfig()) {
WebSock.PrintErrorPage(
t_s("Network was deleted, but config file was not written"));
return true;
}
WebSock.Redirect(GetWebPath() + "edituser?user=" +
pUser->GetUserName().Escape_n(CString::EURL));
return false;
}
bool DelChan(CWebSock& WebSock, CIRCNetwork* pNetwork) {
CString sChan = WebSock.GetParam("name", false);
if (sChan.empty()) {
WebSock.PrintErrorPage(
t_s("That channel doesn't exist for this network"));
return true;
}
pNetwork->DelChan(sChan);
pNetwork->PutIRC("PART " + sChan);
if (!CZNC::Get().WriteConfig()) {
WebSock.PrintErrorPage(
t_s("Channel was deleted, but config file was not written"));
return true;
}
WebSock.Redirect(
GetWebPath() + "editnetwork?user=" +
pNetwork->GetUser()->GetUserName().Escape_n(CString::EURL) +
"&network=" + pNetwork->GetName().Escape_n(CString::EURL));
return false;
}
bool UserPage(CWebSock& WebSock, CTemplate& Tmpl, CUser* pUser = nullptr) {
std::shared_ptr<CWebSession> spSession = WebSock.GetSession();
Tmpl.SetFile("add_edit_user.tmpl");
if (!WebSock.GetParam("submitted").ToUInt()) {
CUser EmptyUser("");
CUser* pRealUser = pUser;
if (pUser) {
Tmpl["Title"] = t_f("Edit User [{1}]")(pUser->GetUserName());
Tmpl["Edit"] = "true";
} else {
CString sUsername = WebSock.GetParam("clone", false);
pUser = CZNC::Get().FindUser(sUsername);
pRealUser = pUser;
if (pUser) {
Tmpl["Title"] =
t_f("Clone User [{1}]")(pUser->GetUserName());
Tmpl["Clone"] = "true";
Tmpl["CloneUsername"] = pUser->GetUserName();
} else {
pUser = &EmptyUser;
Tmpl["Title"] = "Add User";
}
}
Tmpl["ImAdmin"] = CString(spSession->IsAdmin());
Tmpl["Username"] = pUser->GetUserName();
Tmpl["Nick"] = pUser->GetNick();
Tmpl["AltNick"] = pUser->GetAltNick();
Tmpl["StatusPrefix"] = pUser->GetStatusPrefix();
Tmpl["Ident"] = pUser->GetIdent();
Tmpl["RealName"] = pUser->GetRealName();
Tmpl["QuitMsg"] = pUser->GetQuitMsg();
Tmpl["DefaultChanModes"] = pUser->GetDefaultChanModes();
Tmpl["ChanBufferSize"] = CString(pUser->GetChanBufferSize());
Tmpl["QueryBufferSize"] = CString(pUser->GetQueryBufferSize());
Tmpl["TimestampFormat"] = pUser->GetTimestampFormat();
Tmpl["Timezone"] = pUser->GetTimezone();
Tmpl["JoinTries"] = CString(pUser->JoinTries());
Tmpl["NoTrafficTimeout"] = CString(pUser->GetNoTrafficTimeout());
Tmpl["MaxNetworks"] = CString(pUser->MaxNetworks());
Tmpl["MaxJoins"] = CString(pUser->MaxJoins());
Tmpl["MaxQueryBuffers"] = CString(pUser->MaxQueryBuffers());
Tmpl["Language"] = pUser->GetLanguage();
const set<CString>& ssAllowedHosts = pUser->GetAllowedHosts();
for (const CString& sHost : ssAllowedHosts) {
CTemplate& l = Tmpl.AddRow("AllowedHostLoop");
l["Host"] = sHost;
}
const vector<CIRCNetwork*>& vNetworks = pUser->GetNetworks();
for (const CIRCNetwork* pNetwork : vNetworks) {
CTemplate& l = Tmpl.AddRow("NetworkLoop");
l["Name"] = pNetwork->GetName();
l["Username"] = pUser->GetUserName();
l["Clients"] = CString(pNetwork->GetClients().size());
l["IRCNick"] = pNetwork->GetIRCNick().GetNick();
CServer* pServer = pNetwork->GetCurrentServer();
if (pServer) {
l["Server"] = pServer->GetName() + ":" +
(pServer->IsSSL() ? "+" : "") +
CString(pServer->GetPort());
}
}
const MCString& msCTCPReplies = pUser->GetCTCPReplies();
for (const auto& it : msCTCPReplies) {
CTemplate& l = Tmpl.AddRow("CTCPLoop");
l["CTCP"] = it.first + " " + it.second;
}
SCString ssTimezones = CUtils::GetTimezones();
for (const CString& sTZ : ssTimezones) {
CTemplate& l = Tmpl.AddRow("TZLoop");
l["TZ"] = sTZ;
}
#ifdef HAVE_I18N
Tmpl["HaveI18N"] = "true";
// TODO maybe stop hardcoding English here
CTemplate& l_en = Tmpl.AddRow("LanguageLoop");
l_en["Code"] = "";
l_en["Name"] = "English";
for (const auto& it : CTranslationInfo::GetTranslations()) {
CTemplate& lang = Tmpl.AddRow("LanguageLoop");
lang["Code"] = it.first;
lang["Name"] = it.second.sSelfName;
}
#else
Tmpl["HaveI18N"] = "false";
#endif
#ifdef HAVE_ICU
for (const CString& sEncoding : CUtils::GetEncodings()) {
CTemplate& l = Tmpl.AddRow("EncodingLoop");
l["Encoding"] = sEncoding;
}
const CString sEncoding =
pRealUser ? pUser->GetClientEncoding() : "^UTF-8";
if (sEncoding.empty()) {
Tmpl["EncodingUtf"] = "legacy";
} else if (sEncoding[0] == '*') {
Tmpl["EncodingUtf"] = "receive";
Tmpl["Encoding"] = sEncoding.substr(1);
} else if (sEncoding[0] == '^') {
Tmpl["EncodingUtf"] = "send";
Tmpl["Encoding"] = sEncoding.substr(1);
} else {
Tmpl["EncodingUtf"] = "simple";
Tmpl["Encoding"] = sEncoding;
}
Tmpl["LegacyEncodingDisabled"] =
CString(CZNC::Get().IsForcingEncoding());
#else
Tmpl["LegacyEncodingDisabled"] = "true";
Tmpl["EncodingDisabled"] = "true";
Tmpl["EncodingUtf"] = "legacy";
#endif
// To change BindHosts be admin or don't have DenySetBindHost
if (spSession->IsAdmin() ||
!spSession->GetUser()->DenySetBindHost()) {
Tmpl["BindHostEdit"] = "true";
Tmpl["BindHost"] = pUser->GetBindHost();
Tmpl["DCCBindHost"] = pUser->GetDCCBindHost();
}
vector<CString> vDirs;
WebSock.GetAvailSkins(vDirs);
for (const CString& SubDir : vDirs) {
CTemplate& l = Tmpl.AddRow("SkinLoop");
l["Name"] = SubDir;
if (SubDir == pUser->GetSkinName()) {
l["Checked"] = "true";
}
}
set<CModInfo> ssUserMods;
CZNC::Get().GetModules().GetAvailableMods(ssUserMods);
set<CModInfo> ssDefaultMods;
CZNC::Get().GetModules().GetDefaultMods(ssDefaultMods,
CModInfo::UserModule);
for (const CModInfo& Info : ssUserMods) {
CTemplate& l = Tmpl.AddRow("ModuleLoop");
l["Name"] = Info.GetName();
l["Description"] = Info.GetDescription();
l["Wiki"] = Info.GetWikiPage();
l["HasArgs"] = CString(Info.GetHasArgs());
l["ArgsHelpText"] = Info.GetArgsHelpText();
CModule* pModule = nullptr;
pModule = pUser->GetModules().FindModule(Info.GetName());
// Check if module is loaded by all or some networks
const vector<CIRCNetwork*>& userNetworks = pUser->GetNetworks();
unsigned int networksWithRenderedModuleCount = 0;
for (const CIRCNetwork* pCurrentNetwork : userNetworks) {
const CModules& networkModules =
pCurrentNetwork->GetModules();
if (networkModules.FindModule(Info.GetName())) {
networksWithRenderedModuleCount++;
}
}
l["CanBeLoadedByNetwork"] =
CString(Info.SupportsType(CModInfo::NetworkModule));
l["LoadedByAllNetworks"] = CString(
networksWithRenderedModuleCount == userNetworks.size());
l["LoadedBySomeNetworks"] =
CString(networksWithRenderedModuleCount != 0);
if (pModule) {
l["Checked"] = "true";
l["Args"] = pModule->GetArgs();
if (CModInfo::UserModule == GetType() &&
Info.GetName() == GetModName()) {
l["Disabled"] = "true";
}
}
if (!pRealUser) {
for (const CModInfo& DInfo : ssDefaultMods) {
if (Info.GetName() == DInfo.GetName()) {
l["Checked"] = "true";
}
}
}
l["CanBeLoadedGlobally"] =
CString(Info.SupportsType(CModInfo::GlobalModule));
// Check if module is loaded globally
l["LoadedGlobally"] =
CString(CZNC::Get().GetModules().FindModule(
Info.GetName()) != nullptr);
if (!spSession->IsAdmin() && pUser->DenyLoadMod()) {
l["Disabled"] = "true";
}
}
CTemplate& o1 = Tmpl.AddRow("OptionLoop");
o1["Name"] = "autoclearchanbuffer";
o1["DisplayName"] = t_s("Auto Clear Chan Buffer");
o1["Tooltip"] =
t_s("Automatically Clear Channel Buffer After Playback (the "
"default value for new channels)");
if (pUser->AutoClearChanBuffer()) {
o1["Checked"] = "true";
}
/* o2 used to be auto cycle which was removed */
CTemplate& o4 = Tmpl.AddRow("OptionLoop");
o4["Name"] = "multiclients";
o4["DisplayName"] = t_s("Multi Clients");
if (pUser->MultiClients()) {
o4["Checked"] = "true";
}
CTemplate& o7 = Tmpl.AddRow("OptionLoop");
o7["Name"] = "appendtimestamp";
o7["DisplayName"] = t_s("Append Timestamps");
if (pUser->GetTimestampAppend()) {
o7["Checked"] = "true";
}
CTemplate& o8 = Tmpl.AddRow("OptionLoop");
o8["Name"] = "prependtimestamp";
o8["DisplayName"] = t_s("Prepend Timestamps");
if (pUser->GetTimestampPrepend()) {
o8["Checked"] = "true";
}
if (spSession->IsAdmin()) {
CTemplate& o9 = Tmpl.AddRow("OptionLoop");
o9["Name"] = "denyloadmod";
o9["DisplayName"] = t_s("Deny LoadMod");
if (pUser->DenyLoadMod()) {
o9["Checked"] = "true";
}
CTemplate& o10 = Tmpl.AddRow("OptionLoop");
o10["Name"] = "isadmin";
o10["DisplayName"] = t_s("Admin");
if (pUser->IsAdmin()) {
o10["Checked"] = "true";
}
if (pUser == CZNC::Get().FindUser(WebSock.GetUser())) {
o10["Disabled"] = "true";
}
CTemplate& o11 = Tmpl.AddRow("OptionLoop");
o11["Name"] = "denysetbindhost";
o11["DisplayName"] = t_s("Deny SetBindHost");
if (pUser->DenySetBindHost()) {
o11["Checked"] = "true";
}
}
CTemplate& o12 = Tmpl.AddRow("OptionLoop");
o12["Name"] = "autoclearquerybuffer";
o12["DisplayName"] = t_s("Auto Clear Query Buffer");
o12["Tooltip"] =
t_s("Automatically Clear Query Buffer After Playback");
if (pUser->AutoClearQueryBuffer()) {
o12["Checked"] = "true";
}
FOR_EACH_MODULE(i, pUser) {
CTemplate& mod = Tmpl.AddRow("EmbeddedModuleLoop");
mod.insert(Tmpl.begin(), Tmpl.end());
mod["WebadminAction"] = "display";
if ((*i)->OnEmbeddedWebRequest(WebSock, "webadmin/user", mod)) {
mod["Embed"] = WebSock.FindTmpl(*i, "WebadminUser.tmpl");
mod["ModName"] = (*i)->GetModName();
}
}
return true;
}
/* If pUser is nullptr, we are adding a user, else we are editing this
* one */
CString sUsername = WebSock.GetParam("user");
if (!pUser && CZNC::Get().FindUser(sUsername)) {
WebSock.PrintErrorPage(
t_f("Invalid Submission: User {1} already exists")(sUsername));
return true;
}
CUser* pNewUser = GetNewUser(WebSock, pUser);
if (!pNewUser) {
// GetNewUser already called WebSock.PrintErrorPage()
return true;
}
CString sErr;
CString sConfigErrorMsg;
if (!pUser) {
CString sClone = WebSock.GetParam("clone");
if (CUser* pCloneUser = CZNC::Get().FindUser(sClone)) {
pNewUser->CloneNetworks(*pCloneUser);
}
// Add User Submission
if (!CZNC::Get().AddUser(pNewUser, sErr)) {
delete pNewUser;
WebSock.PrintErrorPage(t_f("Invalid submission: {1}")(sErr));
return true;
}
pUser = pNewUser;
sConfigErrorMsg =
t_s("User was added, but config file was not written");
} else {
// Edit User Submission
if (!pUser->Clone(*pNewUser, sErr, false)) {
delete pNewUser;
WebSock.PrintErrorPage(t_f("Invalid submission: {1}")(sErr));
return true;
}
delete pNewUser;
sConfigErrorMsg =
t_s("User was edited, but config file was not written");
}
CTemplate TmplMod;
TmplMod["Username"] = sUsername;
TmplMod["WebadminAction"] = "change";
FOR_EACH_MODULE(it, pUser) {
(*it)->OnEmbeddedWebRequest(WebSock, "webadmin/user", TmplMod);
}
if (!CZNC::Get().WriteConfig()) {
WebSock.PrintErrorPage(sConfigErrorMsg);
return true;
}
if (spSession->IsAdmin() && WebSock.HasParam("submit_return")) {
WebSock.Redirect(GetWebPath() + "listusers");
} else {
WebSock.Redirect(GetWebPath() + "edituser?user=" +
pUser->GetUserName());
}
/* we don't want the template to be printed while we redirect */
return false;
}
bool ListUsersPage(CWebSock& WebSock, CTemplate& Tmpl) {
std::shared_ptr<CWebSession> spSession = WebSock.GetSession();
const map<CString, CUser*>& msUsers = CZNC::Get().GetUserMap();
Tmpl["Title"] = t_s("Manage Users");
Tmpl["Action"] = "listusers";
for (const auto& it : msUsers) {
CTemplate& l = Tmpl.AddRow("UserLoop");
CUser* pUser = it.second;
l["Username"] = pUser->GetUserName();
l["Clients"] = CString(pUser->GetAllClients().size());
l["Networks"] = CString(pUser->GetNetworks().size());
if (pUser == spSession->GetUser()) {
l["IsSelf"] = "true";
}
}
return true;
}
bool TrafficPage(CWebSock& WebSock, CTemplate& Tmpl) {
std::shared_ptr<CWebSession> spSession = WebSock.GetSession();
Tmpl["Title"] = t_s("Traffic Info");
Tmpl["Uptime"] = CZNC::Get().GetUptime();
const map<CString, CUser*>& msUsers = CZNC::Get().GetUserMap();
Tmpl["TotalUsers"] = CString(msUsers.size());
size_t uiNetworks = 0, uiAttached = 0, uiClients = 0, uiServers = 0;
for (const auto& it : msUsers) {
CUser* pUser = it.second;
if (!spSession->IsAdmin() && spSession->GetUser() != it.second) {
continue;
}
vector<CIRCNetwork*> vNetworks = pUser->GetNetworks();
for (const CIRCNetwork* pNetwork : vNetworks) {
uiNetworks++;
if (pNetwork->IsIRCConnected()) {
uiServers++;
}
if (pNetwork->IsNetworkAttached()) {
uiAttached++;
}
uiClients += pNetwork->GetClients().size();
}
uiClients += pUser->GetUserClients().size();
}
Tmpl["TotalNetworks"] = CString(uiNetworks);
Tmpl["AttachedNetworks"] = CString(uiAttached);
Tmpl["TotalCConnections"] = CString(uiClients);
Tmpl["TotalIRCConnections"] = CString(uiServers);
CZNC::TrafficStatsPair Users, ZNC, Total;
CZNC::TrafficStatsMap traffic =
CZNC::Get().GetTrafficStats(Users, ZNC, Total);
for (const auto& it : traffic) {
if (!spSession->IsAdmin() &&
!spSession->GetUser()->GetUserName().Equals(it.first)) {
continue;
}
CTemplate& l = Tmpl.AddRow("TrafficLoop");
l["Username"] = it.first;
l["In"] = CString::ToByteStr(it.second.first);
l["Out"] = CString::ToByteStr(it.second.second);
l["Total"] = CString::ToByteStr(it.second.first + it.second.second);
CZNC::TrafficStatsPair NetworkTotal;
CZNC::TrafficStatsMap NetworkTraffic =
CZNC::Get().GetNetworkTrafficStats(it.first, NetworkTotal);
for (const auto& it2 : NetworkTraffic) {
CTemplate& l2 = Tmpl.AddRow("TrafficLoop");
l2["Network"] = it2.first;
l2["In"] = CString::ToByteStr(it2.second.first);
l2["Out"] = CString::ToByteStr(it2.second.second);
l2["Total"] =
CString::ToByteStr(it2.second.first + it2.second.second);
}
}
Tmpl["UserIn"] = CString::ToByteStr(Users.first);
Tmpl["UserOut"] = CString::ToByteStr(Users.second);
Tmpl["UserTotal"] = CString::ToByteStr(Users.first + Users.second);
Tmpl["ZNCIn"] = CString::ToByteStr(ZNC.first);
Tmpl["ZNCOut"] = CString::ToByteStr(ZNC.second);
Tmpl["ZNCTotal"] = CString::ToByteStr(ZNC.first + ZNC.second);
Tmpl["AllIn"] = CString::ToByteStr(Total.first);
Tmpl["AllOut"] = CString::ToByteStr(Total.second);
Tmpl["AllTotal"] = CString::ToByteStr(Total.first + Total.second);
return true;
}
bool AddListener(CWebSock& WebSock, CTemplate& Tmpl) {
unsigned short uPort = WebSock.GetParam("port").ToUShort();
CString sHost = WebSock.GetParam("host");
CString sURIPrefix = WebSock.GetParam("uriprefix");
if (sHost == "*") sHost = "";
bool bSSL = WebSock.GetParam("ssl").ToBool();
bool bIPv4 = WebSock.GetParam("ipv4").ToBool();
bool bIPv6 = WebSock.GetParam("ipv6").ToBool();
bool bIRC = WebSock.GetParam("irc").ToBool();
bool bWeb = WebSock.GetParam("web").ToBool();
EAddrType eAddr = ADDR_ALL;
if (bIPv4) {
if (bIPv6) {
eAddr = ADDR_ALL;
} else {
eAddr = ADDR_IPV4ONLY;
}
} else {
if (bIPv6) {
eAddr = ADDR_IPV6ONLY;
} else {
WebSock.GetSession()->AddError(
t_s("Choose either IPv4 or IPv6 or both."));
return SettingsPage(WebSock, Tmpl);
}
}
CListener::EAcceptType eAccept;
if (bIRC) {
if (bWeb) {
eAccept = CListener::ACCEPT_ALL;
} else {
eAccept = CListener::ACCEPT_IRC;
}
} else {
if (bWeb) {
eAccept = CListener::ACCEPT_HTTP;
} else {
WebSock.GetSession()->AddError(
t_s("Choose either IRC or HTTP or both."));
return SettingsPage(WebSock, Tmpl);
}
}
CString sMessage;
if (CZNC::Get().AddListener(uPort, sHost, sURIPrefix, bSSL, eAddr,
eAccept, sMessage)) {
if (!sMessage.empty()) {
WebSock.GetSession()->AddSuccess(sMessage);
}
if (!CZNC::Get().WriteConfig()) {
WebSock.GetSession()->AddError(
t_s("Port was changed, but config file was not written"));
}
} else {
WebSock.GetSession()->AddError(sMessage);
}
return SettingsPage(WebSock, Tmpl);
}
bool DelListener(CWebSock& WebSock, CTemplate& Tmpl) {
unsigned short uPort = WebSock.GetParam("port").ToUShort();
CString sHost = WebSock.GetParam("host");
bool bIPv4 = WebSock.GetParam("ipv4").ToBool();
bool bIPv6 = WebSock.GetParam("ipv6").ToBool();
EAddrType eAddr = ADDR_ALL;
if (bIPv4) {
if (bIPv6) {
eAddr = ADDR_ALL;
} else {
eAddr = ADDR_IPV4ONLY;
}
} else {
if (bIPv6) {
eAddr = ADDR_IPV6ONLY;
} else {
WebSock.GetSession()->AddError(t_s("Invalid request."));
return SettingsPage(WebSock, Tmpl);
}
}
CListener* pListener = CZNC::Get().FindListener(uPort, sHost, eAddr);
if (pListener) {
CZNC::Get().DelListener(pListener);
if (!CZNC::Get().WriteConfig()) {
WebSock.GetSession()->AddError(
t_s("Port was changed, but config file was not written"));
}
} else {
WebSock.GetSession()->AddError(
t_s("The specified listener was not found."));
}
return SettingsPage(WebSock, Tmpl);
}
bool SettingsPage(CWebSock& WebSock, CTemplate& Tmpl) {
Tmpl.SetFile("settings.tmpl");
if (!WebSock.GetParam("submitted").ToUInt()) {
Tmpl["Action"] = "settings";
Tmpl["Title"] = t_s("Global Settings");
Tmpl["StatusPrefix"] = CZNC::Get().GetStatusPrefix();
Tmpl["MaxBufferSize"] = CString(CZNC::Get().GetMaxBufferSize());
Tmpl["ConnectDelay"] = CString(CZNC::Get().GetConnectDelay());
Tmpl["ServerThrottle"] = CString(CZNC::Get().GetServerThrottle());
Tmpl["AnonIPLimit"] = CString(CZNC::Get().GetAnonIPLimit());
Tmpl["ProtectWebSessions"] =
CString(CZNC::Get().GetProtectWebSessions());
Tmpl["HideVersion"] = CString(CZNC::Get().GetHideVersion());
const VCString& vsMotd = CZNC::Get().GetMotd();
for (const CString& sMotd : vsMotd) {
CTemplate& l = Tmpl.AddRow("MOTDLoop");
l["Line"] = sMotd;
}
const vector<CListener*>& vpListeners = CZNC::Get().GetListeners();
for (const CListener* pListener : vpListeners) {
CTemplate& l = Tmpl.AddRow("ListenLoop");
l["Port"] = CString(pListener->GetPort());
l["BindHost"] = pListener->GetBindHost();
l["IsHTTP"] = CString(pListener->GetAcceptType() !=
CListener::ACCEPT_IRC);
l["IsIRC"] = CString(pListener->GetAcceptType() !=
CListener::ACCEPT_HTTP);
l["URIPrefix"] = pListener->GetURIPrefix() + "/";
// simple protection for user from shooting his own foot
// TODO check also for hosts/families
// such check is only here, user still can forge HTTP request to
// delete web port
l["SuggestDeletion"] =
CString(pListener->GetPort() != WebSock.GetLocalPort());
#ifdef HAVE_LIBSSL
if (pListener->IsSSL()) {
l["IsSSL"] = "true";
}
#endif
#ifdef HAVE_IPV6
switch (pListener->GetAddrType()) {
case ADDR_IPV4ONLY:
l["IsIPV4"] = "true";
break;
case ADDR_IPV6ONLY:
l["IsIPV6"] = "true";
break;
case ADDR_ALL:
l["IsIPV4"] = "true";
l["IsIPV6"] = "true";
break;
}
#else
l["IsIPV4"] = "true";
#endif
}
vector<CString> vDirs;
WebSock.GetAvailSkins(vDirs);
for (const CString& SubDir : vDirs) {
CTemplate& l = Tmpl.AddRow("SkinLoop");
l["Name"] = SubDir;
if (SubDir == CZNC::Get().GetSkinName()) {
l["Checked"] = "true";
}
}
set<CModInfo> ssGlobalMods;
CZNC::Get().GetModules().GetAvailableMods(ssGlobalMods,
CModInfo::GlobalModule);
for (const CModInfo& Info : ssGlobalMods) {
CTemplate& l = Tmpl.AddRow("ModuleLoop");
CModule* pModule =
CZNC::Get().GetModules().FindModule(Info.GetName());
if (pModule) {
l["Checked"] = "true";
l["Args"] = pModule->GetArgs();
if (CModInfo::GlobalModule == GetType() &&
Info.GetName() == GetModName()) {
l["Disabled"] = "true";
}
}
l["Name"] = Info.GetName();
l["Description"] = Info.GetDescription();
l["Wiki"] = Info.GetWikiPage();
l["HasArgs"] = CString(Info.GetHasArgs());
l["ArgsHelpText"] = Info.GetArgsHelpText();
// Check if the module is loaded by all or some users, and/or by
// all or some networks
unsigned int usersWithRenderedModuleCount = 0;
unsigned int networksWithRenderedModuleCount = 0;
unsigned int networksCount = 0;
const map<CString, CUser*>& allUsers = CZNC::Get().GetUserMap();
for (const auto& it : allUsers) {
const CUser* pUser = it.second;
// Count users which has loaded a render module
const CModules& userModules = pUser->GetModules();
if (userModules.FindModule(Info.GetName())) {
usersWithRenderedModuleCount++;
}
// Count networks which has loaded a render module
const vector<CIRCNetwork*>& userNetworks =
pUser->GetNetworks();
networksCount += userNetworks.size();
for (const CIRCNetwork* pCurrentNetwork : userNetworks) {
if (pCurrentNetwork->GetModules().FindModule(
Info.GetName())) {
networksWithRenderedModuleCount++;
}
}
}
l["CanBeLoadedByNetwork"] =
CString(Info.SupportsType(CModInfo::NetworkModule));
l["LoadedByAllNetworks"] =
CString(networksWithRenderedModuleCount == networksCount);
l["LoadedBySomeNetworks"] =
CString(networksWithRenderedModuleCount != 0);
l["CanBeLoadedByUser"] =
CString(Info.SupportsType(CModInfo::UserModule));
l["LoadedByAllUsers"] =
CString(usersWithRenderedModuleCount == allUsers.size());
l["LoadedBySomeUsers"] =
CString(usersWithRenderedModuleCount != 0);
}
return true;
}
CString sArg;
sArg = WebSock.GetParam("statusprefix");
CZNC::Get().SetStatusPrefix(sArg);
sArg = WebSock.GetParam("maxbufsize");
CZNC::Get().SetMaxBufferSize(sArg.ToUInt());
sArg = WebSock.GetParam("connectdelay");
CZNC::Get().SetConnectDelay(sArg.ToUInt());
sArg = WebSock.GetParam("serverthrottle");
CZNC::Get().SetServerThrottle(sArg.ToUInt());
sArg = WebSock.GetParam("anoniplimit");
CZNC::Get().SetAnonIPLimit(sArg.ToUInt());
sArg = WebSock.GetParam("protectwebsessions");
CZNC::Get().SetProtectWebSessions(sArg.ToBool());
sArg = WebSock.GetParam("hideversion");
CZNC::Get().SetHideVersion(sArg.ToBool());
VCString vsArgs;
WebSock.GetRawParam("motd").Split("\n", vsArgs);
CZNC::Get().ClearMotd();
for (const CString& sMotd : vsArgs) {
CZNC::Get().AddMotd(sMotd.TrimRight_n());
}
CZNC::Get().SetSkinName(WebSock.GetParam("skin"));
set<CString> ssArgs;
WebSock.GetParamValues("loadmod", ssArgs);
for (const CString& s : ssArgs) {
CString sModRet;
CString sModName = s.TrimRight_n("\r");
CString sModLoadError;
if (!sModName.empty()) {
CString sArgs = WebSock.GetParam("modargs_" + sModName);
CModule* pMod = CZNC::Get().GetModules().FindModule(sModName);
if (!pMod) {
if (!CZNC::Get().GetModules().LoadModule(
sModName, sArgs, CModInfo::GlobalModule, nullptr,
nullptr, sModRet)) {
sModLoadError = t_f("Unable to load module [{1}]: {2}")(
sModName, sModRet);
}
} else if (pMod->GetArgs() != sArgs) {
if (!CZNC::Get().GetModules().ReloadModule(
sModName, sArgs, nullptr, nullptr, sModRet)) {
sModLoadError =
t_f("Unable to reload module [{1}]: {2}")(sModName,
sModRet);
}
}
if (!sModLoadError.empty()) {
DEBUG(sModLoadError);
WebSock.GetSession()->AddError(sModLoadError);
}
}
}
const CModules& vCurMods = CZNC::Get().GetModules();
set<CString> ssUnloadMods;
for (const CModule* pCurMod : vCurMods) {
if (ssArgs.find(pCurMod->GetModName()) == ssArgs.end() &&
(CModInfo::GlobalModule != GetType() ||
pCurMod->GetModName() != GetModName())) {
ssUnloadMods.insert(pCurMod->GetModName());
}
}
for (const CString& sMod : ssUnloadMods) {
CZNC::Get().GetModules().UnloadModule(sMod);
}
if (!CZNC::Get().WriteConfig()) {
WebSock.GetSession()->AddError(
t_s("Settings were changed, but config file was not written"));
}
WebSock.Redirect(GetWebPath() + "settings");
/* we don't want the template to be printed while we redirect */
return false;
}
};
template <>
void TModInfo<CWebAdminMod>(CModInfo& Info) {
Info.AddType(CModInfo::UserModule);
Info.SetWikiPage("webadmin");
}
GLOBALMODULEDEFS(CWebAdminMod, "Web based administration module.")