Files
znc/modules/webadmin.cpp
dgw 8d5427cf9b Welcome to 2019
What are we going to do today, Brain?

The same thing we do every year, Pinky: Update all the copyright
headers!

[Skip CI]
2019-01-01 17:05:05 -06:00

2109 lines
78 KiB
C++

/*
* Copyright (C) 2004-2019 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);
}
sArg = WebSock.GetParam("authonlyviamodule");
if (spSession->IsAdmin()) {
if (!sArg.empty()) {
pNewUser->SetAuthOnlyViaModule(sArg.ToBool());
}
} else if (pUser) {
pNewUser->SetAuthOnlyViaModule(pUser->AuthOnlyViaModule());
}
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());
pNewUser->SetAuthOnlyViaModule(
WebSock.GetParam("authonlyviamodule").ToBool());
sArg = WebSock.GetParam("maxnetworks");
if (!sArg.empty()) pNewUser->SetMaxNetworks(sArg.ToUInt());
} else if (pUser) {
pNewUser->SetDenyLoadMod(pUser->DenyLoadMod());
pNewUser->SetDenySetBindHost(pUser->DenySetBindHost());
pNewUser->SetAuthOnlyViaModule(pUser->AuthOnlyViaModule());
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 (!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 (!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 {
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 || 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["AuthOnlyViaModule"] = CString(pUser->AuthOnlyViaModule());
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());
Tmpl["AuthOnlyViaModule"] = CString(CZNC::Get().GetAuthOnlyViaModule());
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());
sArg = WebSock.GetParam("authonlyviamodule");
CZNC::Get().SetAuthOnlyViaModule(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.")