diff --git a/Client.cpp b/Client.cpp index 6036b3f7..422a4386 100644 --- a/Client.cpp +++ b/Client.cpp @@ -12,6 +12,7 @@ #include "IRCSock.h" #include "User.h" #include "znc.h" +#include "WebModules.h" #define CALLMOD(MOD, CLIENT, USER, FUNC) { \ CModule* pModule = CZNC::Get().GetModules().FindModule(MOD); \ @@ -67,8 +68,28 @@ void CClient::ReadLine(const CString& sData) { if (IsAttached()) { MODULECALL(OnUserRaw(sLine), m_pUser, this, return); } else { - if (CZNC::Get().GetModules().OnUnknownUserRaw(this, sLine)) + // If it's an HTTP Request - Check the webmods + if (sLine.WildCmp("GET * HTTP/1.?") || sLine.WildCmp("POST * HTTP/1.?")) { + CModule* pMod = new CModule(NULL, "", ""); + pMod->SetFake(true); + + CWebSock* pSock = new CWebSock(pMod); + CZNC::Get().GetManager().SwapSockByAddr(pSock, this); + + // And don't forget to give it some sane name / timeout + pSock->SetSockName("WebMod::Client"); + pSock->SetTimeout(120); + + // TODO can we somehow get rid of this? + pSock->ReadLine(sLine); + pSock->PushBuff("", 0, true); + return; + } + + if (CZNC::Get().GetModules().OnUnknownUserRaw(this, sLine)) { + return; + } } #endif diff --git a/Makefile.in b/Makefile.in index 19363acd..673d5ae7 100644 --- a/Makefile.in +++ b/Makefile.in @@ -22,7 +22,7 @@ PKGCONFIGDIR := $(libdir)/pkgconfig LIB_SRCS := ZNCString.cpp Csocket.cpp znc.cpp User.cpp IRCSock.cpp Client.cpp DCCBounce.cpp \ DCCSock.cpp Chan.cpp Nick.cpp Server.cpp Modules.cpp MD5.cpp Buffer.cpp Utils.cpp \ - FileUtils.cpp HTTPSock.cpp Template.cpp ClientCommand.cpp Socket.cpp SHA256.cpp + FileUtils.cpp HTTPSock.cpp Template.cpp ClientCommand.cpp Socket.cpp SHA256.cpp WebModules.cpp BIN_SRCS := main.cpp LIB_OBJS := $(patsubst %cpp,%o,$(LIB_SRCS)) BIN_OBJS := $(patsubst %cpp,%o,$(BIN_SRCS)) @@ -75,6 +75,8 @@ install: znc $(LIBZNC) mkdir -p $(DESTDIR)$(bindir) mkdir -p $(DESTDIR)$(includedir)/znc mkdir -p $(DESTDIR)$(PKGCONFIGDIR) + mkdir -p $(DESTDIR)$(libdir)/znc + cp -Rp webskins $(DESTDIR)$(libdir)/znc install -m 0755 znc $(DESTDIR)$(bindir) install -m 0755 znc-config $(DESTDIR)$(bindir) install -m 0755 znc-buildmod $(DESTDIR)$(bindir) diff --git a/Modules.cpp b/Modules.cpp index 5b127b1e..f0012931 100644 --- a/Modules.cpp +++ b/Modules.cpp @@ -12,6 +12,8 @@ #include "User.h" #include "znc.h" #include +#include "WebModules.h" +#include "Template.h" #ifndef RTLD_LOCAL # define RTLD_LOCAL 0 @@ -387,6 +389,10 @@ void CModule::ListSockets() { CString CModule::GetModNick() const { return ((m_pUser) ? m_pUser->GetStatusPrefix() : "*") + m_sModName; } +// Webmods +bool CModule::OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) { return false; } +// !Webmods + bool CModule::OnLoad(const CString& sArgs, CString& sMessage) { sMessage = ""; return true; } bool CModule::OnBoot() { return true; } void CModule::OnPreRehash() {} @@ -701,6 +707,8 @@ bool CModules::LoadModule(const CString& sModule, const CString& sArgs, CUser* p pModule->SetDescription(sDesc); pModule->SetGlobal(bIsGlobal); pModule->SetArgs(sArgs); + DEBUG("********************************* [" + CZNC::Get().GetCurPath() + "] [" + sModPath + "] [" + CDir::ChangeDir(CZNC::Get().GetCurPath(), sModPath) + "]"); + pModule->SetModPath(CDir::ChangeDir(CZNC::Get().GetCurPath(), sModPath)); push_back(pModule); bool bLoaded; diff --git a/Modules.h b/Modules.h index fa61b166..a47d1945 100644 --- a/Modules.h +++ b/Modules.h @@ -11,6 +11,7 @@ #ifndef _MODULES_H #define _MODULES_H +#include "WebModules.h" #include "FileUtils.h" #include "Utils.h" #include @@ -22,6 +23,8 @@ using std::set; class CAuthBase; class CChan; class CClient; +class CWebSock; +class CTemplate; class CIRCSock; // !Forward Declarations @@ -268,6 +271,17 @@ public: * @return false to abort ZNC startup. */ virtual bool OnBoot(); + + // For handling web traffic + virtual bool WebRequiresLogin() { return true; } + virtual bool WebRequiresAdmin() { return false; } + virtual CString GetWebNavTitle() { return ""; } + virtual bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl); + virtual void AddSubPage(TWebSubPage spSubPage) { m_vSubPages.push_back(spSubPage); } + virtual void ClearSubPages() { m_vSubPages.clear(); } + virtual VWebSubPages& GetSubPages() { return m_vSubPages; } + // !Web + /** Called just before znc.conf is rehashed */ virtual void OnPreRehash(); /** This module hook is called after a successful rehash. */ @@ -680,6 +694,7 @@ public: void SetFake(bool b) { m_bFake = b; } void SetGlobal(bool b) { m_bGlobal = b; } void SetDescription(const CString& s) { m_sDescription = s; } + void SetModPath(const CString& s) { m_sModPath = s; } void SetArgs(const CString& s) { m_sArgs = s; } // !Setters @@ -688,6 +703,7 @@ public: bool IsGlobal() const { return m_bGlobal; } const CString& GetDescription() const { return m_sDescription; } const CString& GetArgs() const { return m_sArgs; } + const CString& GetModPath() const { return m_sModPath; } /** @returns For user modules this returns the user for which this * module was loaded. For global modules this returns NULL, @@ -716,8 +732,10 @@ protected: CString m_sDataDir; CString m_sSavePath; CString m_sArgs; + CString m_sModPath; private: MCString m_mssRegistry; //!< way to save name/value pairs. Note there is no encryption involved in this + VWebSubPages m_vSubPages; }; class CModules : public vector { diff --git a/Template.cpp b/Template.cpp index 2a205b46..15481ef8 100644 --- a/Template.cpp +++ b/Template.cpp @@ -90,26 +90,34 @@ void CTemplate::Init() { } */ - ClearPath(); + ClearPaths(); m_pParent = NULL; } -CString CTemplate::ExpandFile(const CString& sFilename) { - if (sFilename.Left(1) == "/" || sFilename.Left(2) == "./") { +CString CTemplate::ExpandFile(const CString& sFilename, bool bFromInc) { + /*if (sFilename.Left(1) == "/" || sFilename.Left(2) == "./") { return sFilename; - } + }*/ - CString sFile(ResolveLiteral(sFilename)); + CString sFile(ResolveLiteral(sFilename).TrimLeft_n("/")); - for (LCString::iterator it = m_lsPaths.begin(); it != m_lsPaths.end(); ++it) { - CString sRoot = *it; + for (list >::iterator it = m_lsbPaths.begin(); it != m_lsbPaths.end(); ++it) { + CString& sRoot = it->first; CString sFilePath(CDir::ChangeDir(sRoot, sFile)); + // Make sure path ends with a slash because "/foo/pub*" matches "/foo/public_keep_out/" but "/foo/pub/*" doesn't + if (!sRoot.empty() && sRoot.Right(1) != "/") { + sRoot += "/"; + } + + if (it->second && !bFromInc) { + DEBUG("\t\tSkipping path (not from INC) [" + sFilePath + "]"); + continue; + } + if (CFile::Exists(sFilePath)) { - // This only works if sRoot got a trailing slash! The - // code which adds paths makes sure this is true. if (sRoot.empty() || sFilePath.Left(sRoot.length()) == sRoot) { - //DEBUG("\t\tFound [" + sFilePath + "]\n"); + DEBUG("\t\tFound [" + sFilePath + "]\n"); return sFilePath; } else { DEBUG("\t\tOutside of root [" + sFilePath + "] !~ [" + sRoot + "]"); @@ -119,15 +127,15 @@ CString CTemplate::ExpandFile(const CString& sFilename) { } } - switch (m_lsPaths.size()) { + switch (m_lsbPaths.size()) { case 0: DEBUG("Unable to find [" + sFile + "] using the current directory"); break; case 1: - DEBUG("Unable to find [" + sFile + "] in the defined path [" + *m_lsPaths.begin()); + DEBUG("Unable to find [" + sFile + "] in the defined path [" + m_lsbPaths.begin()->first + "]"); break; default: - DEBUG("Unable to find [" + sFile + "] in any of the " + CString(m_lsPaths.size()) + " defined paths"); + DEBUG("Unable to find [" + sFile + "] in any of the " + CString(m_lsbPaths.size()) + " defined paths"); } return ""; @@ -138,31 +146,48 @@ void CTemplate::SetPath(const CString& sPaths) { sPaths.Split(":", vsDirs, false); for (size_t a = 0; a < vsDirs.size(); a++) { - AppendPath(vsDirs[a]); + AppendPath(vsDirs[a], false); } } -void CTemplate::PrependPath(const CString& sPath) { - DEBUG("CTemplate::PrependPath(" + sPath + ") == [" + CDir::ChangeDir("./", sPath + "/") + "]"); - m_lsPaths.push_front(CDir::ChangeDir("./", sPath + "/")); +CString CTemplate::MakePath(const CString& sPath) const { + CString sRet(CDir::ChangeDir("./", sPath + "/")); + + if (!sRet.empty() && sRet.Right(1) != "/") { + sRet += "/"; + } + + return sRet; } -void CTemplate::AppendPath(const CString& sPath) { - DEBUG("CTemplate::AppendPath(" + sPath + ") == [" + CDir::ChangeDir("./", sPath + "/") + "]"); - m_lsPaths.push_back(CDir::ChangeDir("./", sPath + "/")); +void CTemplate::PrependPath(const CString& sPath, bool bIncludesOnly) { + DEBUG("CTemplate::PrependPath(" + sPath + ") == [" + MakePath(sPath) + "]"); + m_lsbPaths.push_front(make_pair(MakePath(sPath), bIncludesOnly)); +} + +void CTemplate::AppendPath(const CString& sPath, bool bIncludesOnly) { + DEBUG("CTemplate::AppendPath(" + sPath + ") == [" + MakePath(sPath) + "]"); + m_lsbPaths.push_back(make_pair(MakePath(sPath), bIncludesOnly)); } void CTemplate::RemovePath(const CString& sPath) { DEBUG("CTemplate::RemovePath(" + sPath + ") == [" + CDir::ChangeDir("./", sPath + "/") + "]"); - m_lsPaths.remove(CDir::ChangeDir("./", sPath + "/")); + + for (list >::iterator it = m_lsbPaths.begin(); it != m_lsbPaths.end(); ++it) { + if (it->first == sPath) { + m_lsbPaths.remove(*it); + RemovePath(sPath); // @todo probably shouldn't use recursion, being lazy + return; + } + } } -void CTemplate::ClearPath() { - m_lsPaths.clear(); +void CTemplate::ClearPaths() { + m_lsbPaths.clear(); } bool CTemplate::SetFile(const CString& sFileName) { - m_sFileName = ExpandFile(sFileName); + m_sFileName = ExpandFile(sFileName, false); PrependPath(sFileName + "/.."); if (sFileName.empty()) { @@ -230,6 +255,7 @@ bool CTemplate::PrintString(CString& sRet) { } bool CTemplate::Print(ostream& oOut) { + DEBUG("== Print(o) m_sFileName = [" + m_sFileName + "]"); return Print(m_sFileName, oOut); } @@ -311,7 +337,8 @@ bool CTemplate::Print(const CString& sFileName, ostream& oOut) { if (!uSkip) { if (sAction.Equals("INC")) { - if (!Print(ExpandFile(sArgs), oOut)) { + if (!Print(ExpandFile(sArgs, true), oOut)) { + DEBUG("Unable to print INC'd file [" + sArgs + "]"); return false; } } else if (sAction.Equals("SETOPTION")) { @@ -365,7 +392,7 @@ bool CTemplate::Print(const CString& sFileName, ostream& oOut) { sSetBlockVar = sArgs; bInSetBlock = true; } else if (sAction.Equals("EXPAND")) { - sOutput += ExpandFile(sArgs); + sOutput += ExpandFile(sArgs, true); } else if (sAction.Equals("VAR")) { sOutput += GetValue(sArgs); } else if (sAction.Equals("LT")) { @@ -643,8 +670,20 @@ bool CTemplate::ValidExpr(const CString& sExpression) { } if (sExpr.find("!=") != CString::npos) { + // GOOD: + // >>>>>>>>>>>>>>>>>>> [PageName] [index] -> [PageName != "index"] + // >>>>>>>>>>>>>>>>>>> [PageName] [help] -> [PageName != "help"] + // + // BAD: + // >>>>>>>>>>>>>>>>>>> [PageName] ["index"] -> [PageName != "index"] + // >>>>>>>>>>>>>>>>>>> [PageName] ["help"] -> [PageName != "help"] + // + +//CString CTemplate::Token(const CString& sStr, unsigned int uPos, bool bRest, const CString& sSep, bool bAllowEmpty, + // const CString& sLeft, const CString& sRight, bool bTrimQuotes) { sName = sExpr.Token(0, false, "!=").Trim_n(); sValue = sExpr.Token(1, true, "!=", false, "\"", "\"", true).Trim_n(); + DEBUG(">>>>>>>>>>>>>>>>>>> [" + sName + "] [" + sValue + "] -> [" + sExpr + "]"); bNegate = !bNegate; } else if (sExpr.find("==") != CString::npos) { sName = sExpr.Token(0, false, "==").Trim_n(); diff --git a/Template.h b/Template.h index 19ae3a48..61b50b8f 100644 --- a/Template.h +++ b/Template.h @@ -145,14 +145,15 @@ public: void Init(); CTemplate* GetParent(bool bRoot); - CString ExpandFile(const CString& sFilename); + CString ExpandFile(const CString& sFilename, bool bFromInc = false); bool SetFile(const CString& sFileName); void SetPath(const CString& sPath); // Sets the dir:dir:dir type path to look at for templates, as of right now no ../../.. protection - void PrependPath(const CString& sPath); - void AppendPath(const CString& sPath); + CString MakePath(const CString& sPath) const; + void PrependPath(const CString& sPath, bool bIncludesOnly = false); + void AppendPath(const CString& sPath, bool bIncludesOnly = false); void RemovePath(const CString& sPath); - void ClearPath(); + void ClearPaths(); CString ResolvePath(const CString& sPath, const CString& sFilename); bool PrintString(CString& sRet); bool Print(ostream& oOut = cout); @@ -175,7 +176,7 @@ public: private: CTemplate* m_pParent; CString m_sFileName; - LCString m_lsPaths; + list > m_lsbPaths; map > m_mvLoops; vector m_vLoopContexts; CSmartPtr m_spOptions; diff --git a/User.cpp b/User.cpp index 9fd0ad5b..ea1f0775 100644 --- a/User.cpp +++ b/User.cpp @@ -358,6 +358,7 @@ bool CUser::Clone(const CUser& User, CString& sErrorRet, bool bCloneChans) { SetVHost(User.GetVHost()); SetDCCVHost(User.GetDCCVHost()); SetQuitMsg(User.GetQuitMsg()); + SetSkinName(User.GetSkinName()); SetDefaultChanModes(User.GetDefaultChanModes()); SetBufferCount(User.GetBufferCount()); SetJoinTries(User.JoinTries()); @@ -645,6 +646,7 @@ bool CUser::WriteConfig(CFile& File) { PrintLine(File, "QuitMsg", GetQuitMsg()); if (CZNC::Get().GetStatusPrefix() != GetStatusPrefix()) PrintLine(File, "StatusPrefix", GetStatusPrefix()); + PrintLine(File, "Skin", GetSkinName()); PrintLine(File, "ChanModes", GetDefaultChanModes()); PrintLine(File, "Buffer", CString(GetBufferCount())); PrintLine(File, "KeepBuffer", CString(KeepBuffer())); @@ -1247,4 +1249,6 @@ CString CUser::GetQuitMsg() const { return (!m_sQuitMsg.Trim_n().empty()) ? m_sQ const MCString& CUser::GetCTCPReplies() const { return m_mssCTCPReplies; } unsigned int CUser::GetBufferCount() const { return m_uBufferCount; } bool CUser::KeepBuffer() const { return m_bKeepBuffer; } +//CString CUser::GetSkinName() const { return (!m_sSkinName.empty()) ? m_sSkinName : CZNC::Get().GetSkinName(); } +CString CUser::GetSkinName() const { return m_sSkinName; } // !Getters diff --git a/User.h b/User.h index aeb6d0b8..825e6242 100644 --- a/User.h +++ b/User.h @@ -138,6 +138,29 @@ public: void AddBytesRead(unsigned long long u) { m_uBytesRead += u; } void AddBytesWritten(unsigned long long u) { m_uBytesWritten += u; } + // Counter for logging out of the web interface + unsigned int GetWebLogoutCounter(const CString& sToken) { + map::iterator it = m_suWebLogoutCounters.find(sToken); + + if (it == m_suWebLogoutCounters.end()) { + m_suWebLogoutCounters[sToken] = 1; + return 1; + } + + return it->second; + } + + unsigned int IncWebLogoutCounter(const CString& sToken) { + map::iterator it = m_suWebLogoutCounters.find(sToken); + + if (it == m_suWebLogoutCounters.end()) { + m_suWebLogoutCounters[sToken] = 2; + return 2; + } + + return ++it->second; + } + // Setters void SetUserName(const CString& s); void SetNick(const CString& s); @@ -169,6 +192,7 @@ public: void SetTimezoneOffset(float b) { m_fTimezoneOffset = b; } void SetJoinTries(unsigned int i) { m_uMaxJoinTries = i; } void SetMaxJoins(unsigned int i) { m_uMaxJoins = i; } + void SetSkinName(const CString& s) { m_sSkinName = s; } void SetIRCConnectEnabled(bool b) { m_bIRCConnectEnabled = b; } void SetIRCAway(bool b) { m_bIRCAway = b; } // !Setters @@ -223,6 +247,7 @@ public: unsigned long long BytesWritten() const { return m_uBytesWritten; } unsigned int JoinTries() const { return m_uMaxJoinTries; } unsigned int MaxJoins() const { return m_uMaxJoins; } + CString GetSkinName() const; // !Getters private: protected: @@ -284,6 +309,9 @@ protected: unsigned long long m_uBytesWritten; unsigned int m_uMaxJoinTries; unsigned int m_uMaxJoins; + CString m_sSkinName; + + map m_suWebLogoutCounters; #ifdef _MODULES CModules* m_pModules; diff --git a/WebModules.cpp b/WebModules.cpp new file mode 100644 index 00000000..3f5f54e9 --- /dev/null +++ b/WebModules.cpp @@ -0,0 +1,547 @@ +#include "WebModules.h" +#include "User.h" +#include "znc.h" +#include + + +CZNCTagHandler::CZNCTagHandler(CWebSock& WebSock) : CTemplateTagHandler(), m_WebSock(WebSock) { +} + +bool CZNCTagHandler::HandleTag(CTemplate& Tmpl, const CString& sName, const CString& sArgs, CString& sOutput) { + if (sName.Equals("URLPARAM")) { + //sOutput = CZNC::Get() + std::cerr << "========================= URLPARAM !!!!!!!!!!" << std::endl; + sOutput = m_WebSock.GetParam(sArgs.Token(0)); + return true; + } + + return false; +} + +CWebAuth::CWebAuth(CWebSock* pWebSock, const CString& sUsername, const CString& sPassword) + : CAuthBase(sUsername, sPassword, pWebSock) { + m_pWebSock = pWebSock; +} + +void CWebAuth::AcceptedLogin(CUser& User) { + if (m_pWebSock) { + m_pWebSock->SetSessionUser(&User); + m_pWebSock->SetLoggedIn(true); + m_pWebSock->UnPauseRead(); + } +} + +void CWebAuth::RefusedLogin(const CString& sReason) { + if (m_pWebSock) { + m_pWebSock->SetSessionUser(NULL); + m_pWebSock->SetLoggedIn(false); + m_pWebSock->UnPauseRead(); + } +} + +CWebSock::CWebSock(CModule* pModule) : CHTTPSock(pModule) { + m_pModule = pModule; + m_pSessionUser = NULL; + m_bPathsSet = false; + + m_Template.AddTagHandler(new CZNCTagHandler(*this)); +} + +CWebSock::CWebSock(CModule* pModule, const CString& sHostname, unsigned short uPort, int iTimeout) + : CHTTPSock(pModule, sHostname, uPort, iTimeout) { + m_pModule = pModule; + m_pSessionUser = NULL; + m_bPathsSet = false; + + m_Template.AddTagHandler(new CZNCTagHandler(*this)); +} + +CWebSock::~CWebSock() { + if (!m_spAuth.IsNull()) { + CWebAuth* pAuth = (CWebAuth*) &(*m_spAuth); + pAuth->SetWebSock(NULL); + } + + // If the module IsFake() then it was created as a dummy and needs to be deleted + CModule* pMod = GetModule(); + if (pMod && pMod->IsFake()) { + pMod->UnlinkSocket(this); + delete pMod; + } +} + +void CWebSock::ParsePath() { + // The URI looks like: + // /[user:][module][/page][?arg1=val1&arg2=val2...] + + m_sForceUser.clear(); + + m_sPath = GetPath().TrimLeft_n("/"); + + m_sPath.TrimPrefix("mods/"); + m_sPath.TrimPrefix("modfiles/"); + + m_sModName = m_sPath.Token(0, false, "/"); + m_sPage = m_sPath.Token(1, true, "/"); + + if (m_sModName.find(":") != CString::npos) { + m_sForceUser = m_sModName.Token(0, false, ":"); + m_sModName = m_sModName.Token(1, false, ":"); + } + + if (m_sPage.empty()) { + m_sPage = "index"; + } + + DEBUG("Path [" + m_sPath + "]"); + DEBUG("User [" + m_sForceUser + "]"); + DEBUG("Module [" + m_sModName + "]"); + DEBUG("Page [" + m_sPage + "]"); +} + +CModule* CWebSock::ResolveModule() { + ParsePath(); + CModule* pModRet = NULL; + + // Dot means static file, not module + if (m_sModName.find(".") != CString::npos) { + return NULL; + } + + // First look for forced user-mods + if (!m_sForceUser.empty()) { + CUser* pUser = CZNC::Get().FindUser(m_sForceUser); + + if (pUser) { + pModRet = pUser->GetModules().FindModule(m_sModName); + } else { + DEBUG("User not found while trying to handle web requrest for [" + m_sPage + "]"); + } + } else { + // This could be user level or global level, check both + pModRet = CZNC::Get().GetModules().FindModule(m_sModName); + + if (!pModRet) { + // It's not a loaded global module and it has no forced username so we + // have to force a login to try a module loaded by the current user + if (!ForceLogin()) { + return NULL; + } + + pModRet = m_pSessionUser->GetModules().FindModule(m_sModName); + } + } + + if (!pModRet) { + DEBUG("Module not found"); + } else if (pModRet->IsFake()) { + DEBUG("Fake module found, ignoring"); + pModRet = NULL; + } + + return pModRet; +} + +size_t CWebSock::GetAvailSkins(vector& vRet) { + vRet.clear(); + + CString sRoot(GetSkinPath("_default_")); + + sRoot.TrimRight("/"); + sRoot.TrimRight("_default_"); + sRoot.TrimRight("/"); + + if (!sRoot.empty()) { + sRoot += "/"; + } + + if (!sRoot.empty() && CFile::IsDir(sRoot)) { + CDir Dir(sRoot); + + for (unsigned int d = 0; d < Dir.size(); d++) { + const CFile& SubDir = *Dir[d]; + + if (SubDir.IsDir() && SubDir.GetShortName() == "_default_") { + vRet.push_back(SubDir); + } + } + + for (unsigned int e = 0; e < Dir.size(); e++) { + const CFile& SubDir = *Dir[e]; + + if (SubDir.IsDir() && SubDir.GetShortName() != "_default_" && SubDir.GetShortName() != ".svn") { + vRet.push_back(SubDir); + } + } + } + + return vRet.size(); +} + +void CWebSock::SetPaths(CModule* pModule, bool bIsTemplate) { + m_Template.ClearPaths(); + + CString sHomeSkinsDir(CZNC::Get().GetZNCPath() + "/webskins/"); + CString sSkinName(GetSkinName()); + + DEBUG("---- sHomeSkinsDir=[" + sHomeSkinsDir + "] sSkinName=[" + sSkinName + "]"); + + // Module specific paths + + if (pModule) { + const CString& sModName(pModule->GetModName()); + + // 1. ~/.znc/webskins//mods// + // + if (!sSkinName.empty()) { + m_Template.AppendPath(GetSkinPath(sSkinName) + "/mods/" + sModName + "/"); + } + + // 2. ~/.znc/webskins/_default_/mods// + // + m_Template.AppendPath(GetSkinPath("_default_") + "/mods/" + sModName + "/"); + + // 3. ./modules// + // + m_Template.AppendPath(GetModWebPath(sModName)); + + // 4. ~/.znc/webskins//mods// + // + if (!sSkinName.empty()) { + m_Template.AppendPath(GetSkinPath(sSkinName) + "/mods/" + sModName + "/"); + } + + // 5. ~/.znc/webskins/_default_/mods// + // + m_Template.AppendPath(GetSkinPath("_default_") + "/mods/" + sModName + "/"); + } + + // 6. ~/.znc/webskins// + // + if (!sSkinName.empty()) { + m_Template.AppendPath(GetSkinPath(sSkinName) + CString(bIsTemplate ? "/tmpl/" : "/"), (0 && pModule != NULL)); + } + + // 7. ~/.znc/webskins/_default_/ + // + m_Template.AppendPath(GetSkinPath("_default_") + CString(bIsTemplate ? "/tmpl/" : "/"), (0 && pModule != NULL)); + + m_bPathsSet = true; +} + +void CWebSock::SetVars() { + m_Template["SessionUser"] = GetUser(); + m_Template["SessionIP"] = GetRemoteIP(); + m_Template["Tag"] = CZNC::GetTag(); + m_Template["SkinName"] = GetSkinName(); + + if (IsAdmin()) { + m_Template["IsAdmin"] = "true"; + } + + // Global Mods + CGlobalModules& vgMods = CZNC::Get().GetModules(); + for (unsigned int a = 0; a < vgMods.size(); a++) { + AddModLoop("GlobalModLoop", *vgMods[a]); + } + + // User Mods + if (IsLoggedIn()) { + CModules& vMods = m_pSessionUser->GetModules(); + + for (unsigned int a = 0; a < vMods.size(); a++) { + AddModLoop("UserModLoop", *vMods[a]); + } + } + + if (IsLoggedIn()) { + m_Template["LoggedIn"] = "true"; + } +} + +bool CWebSock::AddModLoop(const CString& sLoopName, CModule& Module) { + CString sTitle(Module.GetWebNavTitle()); + + DEBUG("=== === === === === [" + Module.GetModName() + "] [" + CString(IsLoggedIn()) + "]"); + + if (!sTitle.empty() && (IsLoggedIn() || (!Module.WebRequiresLogin() && !Module.WebRequiresAdmin())) && (IsAdmin() || !Module.WebRequiresAdmin())) { + CTemplate& Row = m_Template.AddRow(sLoopName); + + Row["ModName"] = Module.GetModName(); + Row["Title"] = sTitle; + + if (m_sModName == Module.GetModName()) { + Row["Active"] = "true"; + } + + if (Module.GetUser()) { + Row["Username"] = Module.GetUser()->GetUserName(); + } + + VWebSubPages& vSubPages = Module.GetSubPages(); + + for (unsigned int a = 0; a < vSubPages.size(); a++) { + CTemplate& SubRow = Row.AddRow("SubPageLoop"); + TWebSubPage& SubPage = vSubPages[a]; + + SubRow["ModName"] = Module.GetModName(); + SubRow["PageName"] = SubPage->GetName(); + SubRow["Title"] = SubPage->GetTitle().empty() ? SubPage->GetName() : SubPage->GetTitle(); + + CString& sParams = SubRow["Params"]; + + // bActive is whether or not the current url matches this subpage (including the params below) + bool bActive = (m_sModName == Module.GetModName() && m_sPage == SubPage->GetName()); + + const VPair& vParams = SubPage->GetParams(); + for (size_t b = 0; b < vParams.size(); b++) { + pair ssNV = vParams[b]; + + if (!sParams.empty()) { + sParams += "&"; + } + + if (!ssNV.first.empty()) { + if (!ssNV.second.empty()) { + sParams += ssNV.first.Escape_n(CString::EURL); + sParams += "="; + sParams += ssNV.second.Escape_n(CString::EURL); + } + + if (bActive && GetParam(ssNV.first) != ssNV.second) { + bActive = false; + } + } + } + + if (bActive) { + SubRow["Active"] = "true"; + } + } + + return true; + } + + return false; +} + +bool CWebSock::PrintStaticFile(const CString& sPath, CString& sPageRet, CModule* pModule) { + SetPaths(pModule); + DEBUG("About to print [" + m_Template.ExpandFile(sPath) + "]"); + return PrintFile(m_Template.ExpandFile(sPath.TrimLeft_n("/"))); +} + +bool CWebSock::PrintTemplate(const CString& sPageName, CString& sPageRet, CModule* pModule) { + SetVars(); + m_Template["PageName"] = sPageName; + + if (pModule) { + CUser* pUser = pModule->GetUser(); + m_Template["ModUser"] = pUser ? pUser->GetUserName() : ""; + m_Template["ModName"] = pModule->GetModName(); + + if (m_Template.find("Title") == m_Template.end()) { + m_Template["Title"] = pModule->GetWebNavTitle(); + } + } + + if (!m_bPathsSet) { + SetPaths(pModule, true); + } + + if (m_Template.GetFileName().empty() && !m_Template.SetFile(sPageName + ".tmpl")) { + return false; + } + + return m_Template.PrintString(sPageRet); +} + +CString CWebSock::GetModWebPath(const CString& sModName) const { + CString sRet = CZNC::Get().GetCurPath() + "/modules/www/" + sModName; + + if (!CFile::IsDir(sRet)) { + sRet = CString(_MODDIR_) + "/www/" + sModName; + } + + return sRet + "/"; +} + +CString CWebSock::GetSkinPath(const CString& sSkinName) const { + CString sRet = CZNC::Get().GetZNCPath() + "/webskins/" + sSkinName; + + if (!CFile::IsDir(sRet)) { + sRet = CZNC::Get().GetCurPath() + "/webskins/" + sSkinName; + + if (!CFile::IsDir(sRet)) { + sRet = CString(_SKINDIR_) + "/" + sSkinName; + } + } + + return sRet + "/"; +} + +bool CWebSock::OnPageRequest(const CString& sURI, CString& sPageRet) { + DEBUG("CWebSock::OnPageRequest(" + sURI + ")"); + + // Handle the static pages that don't require a login + if (sURI == "/") { + return PrintTemplate("index", sPageRet); + } else if (sURI == "/favicon.ico") { + return PrintStaticFile("/pub/favicon.ico", sPageRet); + } else if (sURI == "/logout") { + if (!IsLoggedIn()) { + Redirect("/"); + return true; + } + + unsigned int uCurCnt = GetParam("cnt").ToUInt(); + unsigned int uCounter = m_pSessionUser->GetWebLogoutCounter(GetRemoteIP()); + + if (!uCurCnt) { + Redirect("/logout?cnt=" + CString(uCounter)); + return true; + } + + if (uCurCnt >= uCounter) { + m_bLoggedIn = false; + m_pSessionUser->IncWebLogoutCounter(GetRemoteIP()); + ForceLogin(); + } else { + Redirect("/"); + } + + return true; + } else if (sURI.Left(6) == "/login") { + if (ForceLogin()) { + Redirect("/"); + } + + return true; + } else if (sURI.Left(5) == "/pub/") { + return PrintStaticFile(sURI, sPageRet); + } else if (sURI.Left(6) == "/mods/" || sURI.Left(10) == "/modfiles/") { + ParsePath(); + // Make sure modules are treated as directories + if (sURI.Right(1) != "/" && sURI.find(".") == CString::npos && sURI.TrimLeft_n("/mods/").TrimLeft_n("/").find("/") == CString::npos) { + Redirect(sURI + "/"); + return true; + } + + if (m_sModName.empty()) { + return PrintTemplate("modlist", sPageRet); + } + + DEBUG("FindModule(" + m_sModName + ", " + m_sForceUser + ")"); + CModule* pModule = CZNC::Get().FindModule(m_sModName, m_sForceUser); + + if (!pModule && m_sForceUser.empty()) { + if (!ForceLogin()) { + return true; + } + + pModule = CZNC::Get().FindModule(m_sModName, m_pSessionUser); + } + + if (!pModule) { + return false; + } else if (pModule->WebRequiresLogin() && !ForceLogin()) { + return true; + } else if (pModule->WebRequiresAdmin() && !IsAdmin()) { + sPageRet = GetErrorPage(403, "Forbidden", "You need to be an admin to access this module"); + return true; + } else if (pModule && !pModule->IsGlobal() && pModule->GetUser() != m_pSessionUser) { + sPageRet = GetErrorPage(403, "Forbidden", "You must login as " + pModule->GetUser()->GetUserName() + " in order to view this page"); + return true; + } + + if (pModule && !pModule->IsGlobal() && (!IsLoggedIn() || pModule->GetUser() != GetSessionUser())) { + AddModLoop("UserModLoop", *pModule); + } + + if (sURI.Left(10) == "/modfiles/") { + m_Template.AppendPath(GetSkinPath(GetSkinName()) + "/mods/" + m_sModName + "/files/"); + m_Template.AppendPath(GetModWebPath(m_sModName) + "/files/"); + + std::cerr << "===================== fffffffffffffffffffff [" << m_sModName << "] [" << m_sPage << "]" << std::endl; + return PrintFile(m_Template.ExpandFile(m_sPage.TrimLeft_n("/"))); + } else { + SetPaths(pModule, true); + + if (pModule->OnWebRequest(*this, m_sPage, m_Template)) { + return PrintTemplate(m_sPage, sPageRet, pModule); + } + + sPageRet = GetErrorPage(404, "Not Implemented", "The requested module does not acknowledge web requests"); + return false; + } + } else { + CString sPage(sURI.Trim_n("/")); + if (sPage.length() < 32) { + for (unsigned int a = 0; a < sPage.length(); a++) { + unsigned char c = sPage[a]; + + if ((c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && c != '_') { + return false; + } + } + + return PrintTemplate(sPage, sPageRet); + } + } + + return false; +} + +void CWebSock::PrintErrorPage(const CString& sMessage) { + m_Template.SetFile("Error.tmpl"); + + m_Template["Action"] = "error"; + m_Template["Title"] = "Error"; + m_Template["Error"] = sMessage; +} + +size_t CWebSock::AddError(const CString& sMessage) { + CTemplate& Row = m_Template.AddRow("ErrorLoop"); + + Row["Message"] = sMessage; + return m_Template.GetLoop("ErrorLoop")->size(); +} + +size_t CWebSock::AddSuccess(const CString& sMessage) { + CTemplate& Row = m_Template.AddRow("SuccessLoop"); + + Row["Message"] = sMessage; + return m_Template.GetLoop("SuccessLoop")->size(); +} + +bool CWebSock::OnLogin(const CString& sUser, const CString& sPass) { + DEBUG("=================== CWebSock::OnLogin()"); + m_spAuth = new CWebAuth(this, sUser, sPass); + + // Some authentication module could need some time, block this socket + // until then. CWebAuth will UnPauseRead(). + PauseRead(); + CZNC::Get().AuthUser(m_spAuth); + + // If CWebAuth already set this, don't change it. + return IsLoggedIn(); +} + +Csock* CWebSock::GetSockObj(const CString& sHost, unsigned short uPort) { + CWebSock* pSock = new CWebSock(GetModule(), sHost, uPort); + pSock->SetSockName("Web::Client"); + pSock->SetTimeout(120); + + return pSock; +} + +bool CWebSock::IsAdmin() const { return m_pSessionUser && m_pSessionUser->IsAdmin(); } +CUser* CWebSock::GetSessionUser() const { return m_pSessionUser; } +CString CWebSock::GetSkinName() const { + if (m_pSessionUser && IsLoggedIn() && !m_pSessionUser->GetSkinName().empty()) { + return m_pSessionUser->GetSkinName(); + } + + return CZNC::Get().GetSkinName(); +} + diff --git a/WebModules.h b/WebModules.h new file mode 100644 index 00000000..11e63760 --- /dev/null +++ b/WebModules.h @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2004-2009 See the AUTHORS file for details. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#ifndef _WEBMODULES_H +#define _WEBMODULES_H + +#include "Client.h" +#include "Template.h" +#include "HTTPSock.h" +#include "FileUtils.h" + +class CWebSock; +class CModule; +class CWebSubPage; + +typedef CSmartPtr TWebSubPage; +typedef vector VWebSubPages; + +class CZNCTagHandler : public CTemplateTagHandler { +public: + CZNCTagHandler(CWebSock& pWebSock); + virtual ~CZNCTagHandler() {} + + virtual bool HandleTag(CTemplate& Tmpl, const CString& sName, const CString& sArgs, CString& sOutput); +private: + CWebSock& m_WebSock; +}; + + +class CWebSubPage { +public: + CWebSubPage(const CString& sName, const CString& sTitle = "") : m_sName(sName), m_sTitle(sTitle) { + } + + CWebSubPage(const CString& sName, const CString& sTitle, const VPair& vParams) : m_sName(sName), m_sTitle(sTitle), m_vParams(vParams) {} + virtual ~CWebSubPage() {} + + void SetName(const CString& s) { m_sName = s; } + void SetTitle(const CString& s) { m_sTitle = s; } + void AddParam(const CString& sName, const CString& sValue) { m_vParams.push_back(make_pair(sName, sValue)); } + + const CString& GetName() const { return m_sName; } + const CString& GetTitle() const { return m_sTitle; } + const VPair& GetParams() const { return m_vParams; } + +private: + CString m_sName; + CString m_sTitle; + VPair m_vParams; +}; + +class CWebAuth : public CAuthBase { +public: + CWebAuth(CWebSock* pWebSock, const CString& sUsername, const CString& sPassword); + virtual ~CWebAuth() {} + + void SetWebSock(CWebSock* pWebSock) { m_pWebSock = pWebSock; } + void AcceptedLogin(CUser& User); + void RefusedLogin(const CString& sReason); +private: +protected: + CWebSock* m_pWebSock; +}; + + +class CWebSock : public CHTTPSock { +public: + CWebSock(CModule* pModule); + CWebSock(CModule* pModule, const CString& sHostname, unsigned short uPort, int iTimeout = 60); + virtual ~CWebSock(); + + virtual bool OnPageRequest(const CString& sURI, CString& sPageRet); + + virtual bool OnLogin(const CString& sUser, const CString& sPass); + + void ParsePath(); // This parses the path portion of the url into some member vars + CModule* ResolveModule(); + + //virtual bool PrintFile(const CString& sFileName, CString sContentType = ""); + bool PrintTemplate(const CString& sPageName, CString& sPageRet, CModule* pModule = NULL); + bool PrintStaticFile(const CString& sPath, CString& sPageRet, CModule* pModule = NULL); + + bool AddModLoop(const CString& sLoopName, CModule& Module); + void SetPaths(CModule* pModule, bool bIsTemplate = false); + void SetVars(); + + void FillErrorPage(CString& sPageRet, const CString& sError) { + m_Template["Action"] = "error"; + m_Template["Title"] = "Error"; + m_Template["Error"] = sError; + + PrintTemplate("error", sPageRet); + } + + void PrintErrorPage(const CString& sMessage); + size_t AddError(const CString& sMessage); + size_t AddSuccess(const CString& sMessage); + + // Setters + void SetSessionUser(CUser* p) { + m_pSessionUser = p; + } + // !Setters + + virtual Csock* GetSockObj(const CString& sHost, unsigned short uPort); + bool IsAdmin() const; + CUser* GetSessionUser() const; + CString GetModWebPath(const CString& sModName) const; + CString GetSkinPath(const CString& sSkinName) const; + CModule* GetModule() const { return (CModule*) m_pModule; } + size_t GetAvailSkins(vector& vRet); + CString GetSkinName() const; + +private: + CUser* m_pSessionUser; + bool m_bPathsSet; + CTemplate m_Template; + CSmartPtr m_spAuth; + CString m_sForceUser; // Gets filled by ResolveModule() + CString m_sModName; // Gets filled by ResolveModule() + CString m_sPath; // Gets filled by ResolveModule() + CString m_sPage; // Gets filled by ResolveModule() +}; + +#endif // !_WEBMODULES_H diff --git a/main.h b/main.h index 47478dd0..e5a21054 100644 --- a/main.h +++ b/main.h @@ -24,6 +24,10 @@ #define _MODDIR_ "/usr/lib/znc" #endif +#ifndef _SKINDIR_ +#define _SKINDIR_ _MODDIR_ "/webskins" +#endif + #ifndef _DATADIR_ #define _DATADIR_ "/usr/share/znc" #endif diff --git a/modules/Makefile.in b/modules/Makefile.in index 19c6c1f8..060f5c54 100644 --- a/modules/Makefile.in +++ b/modules/Makefile.in @@ -87,6 +87,7 @@ create_install_dir: rm -rf $(DESTDIR)$(MODDIR)/*.so install_metadirs: create_install_dir + cp -Rp $(srcdir)/www $(DESTDIR)$(MODDIR) for a in $(srcdir)/*; do \ d=$$(echo $$a | sed -e "s:$(srcdir)/::g"); \ if [ -d $$a ] && [ -f $${d}.so ]; then \ diff --git a/modules/notes.cpp b/modules/notes.cpp new file mode 100644 index 00000000..b9532be3 --- /dev/null +++ b/modules/notes.cpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2004-2010 See the AUTHORS file for details. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include "Chan.h" +#include "HTTPSock.h" +#include "Server.h" +#include "Template.h" +#include "User.h" +#include "znc.h" +#include "WebModules.h" +#include + +using std::stringstream; + +class CNotesMod : public CModule { +public: + MODCONSTRUCTOR(CNotesMod) { + } + + virtual ~CNotesMod() { + } + + virtual bool OnLoad(const CString& sArgStr, CString& sMessage) { + return true; + } + + virtual bool WebRequiresLogin() { return true; } + virtual bool WebRequiresAdmin() { return false; } + virtual CString GetWebNavTitle() { return "Notes"; } + + virtual void OnClientLogin() { + ListNotes(true); + } + + virtual EModRet OnUserRaw(CString& sLine) { + if (sLine.Left(1) != "#") { + return CONTINUE; + } + + CString sKey; + bool bOverwrite = false; + + if (sLine == "#?") { + ListNotes(true); + } else if (sLine.Left(2) == "#-") { + sKey = sLine.Token(0).LeftChomp_n(2); + if (DelNote(sKey)) { + PutModNotice("Deleted note [" + sKey + "]"); + } else { + PutModNotice("Unable to delete note [" + sKey + "]"); + } + } else if (sLine.Left(2) == "#+") { + sKey = sLine.Token(0).LeftChomp_n(2); + bOverwrite = true; + } else if (sLine.Left(1) == "#") { + sKey = sLine.Token(0).LeftChomp_n(1); + } + + CString sValue(sLine.Token(1, true)); + + if (!sKey.empty()) { + if (!bOverwrite && !GetNV(sKey).empty()) { + PutModNotice("That note already exists. Use /#+ to overwrite."); + } else if (AddNote(sKey, sValue)) { + if (!bOverwrite) { + PutModNotice("Added note [" + sKey + "]"); + } else { + PutModNotice("Set note for [" + sKey + "]"); + } + } else { + PutModNotice("Unable to add note [" + sKey + "]"); + } + } + + return HALT; + } + + bool DelNote(const CString& sKey) { + return DelNV(sKey); + } + + bool AddNote(const CString& sKey, const CString& sNote) { + if (sKey.empty()) { + return false; + } + + return SetNV(sKey, sNote); + } + + void ListNotes(bool bNotice = false) { + CClient* pClient = GetClient(); + + if (pClient) { + CTable Table; + Table.AddColumn("Key"); + Table.AddColumn("Note"); + + for (MCString::iterator it = BeginNV(); it != EndNV(); ++it) { + Table.AddRow(); + Table.SetCell("Key", it->first); + Table.SetCell("Note", it->second); + } + + if (Table.size()) { + unsigned int idx = 0; + CString sLine; + while (Table.GetLine(idx++, sLine)) { + if (bNotice) { + pClient->PutModNotice(GetModName(), sLine); + } else { + pClient->PutModule(GetModName(), sLine); + } + } + } else { + if (bNotice) { + PutModNotice("You have no entries."); + } else { + PutModule("You have no entries."); + } + } + } + } + + virtual void OnModCommand(const CString& sLine) { + CString sCmd(sLine.Token(0)); + + if (sLine.Equals("LIST")) { + ListNotes(); + } else if (sCmd.Equals("ADD")) { + CString sKey(sLine.Token(1)); + CString sValue(sLine.Token(2, true)); + + if (!GetNV(sKey).empty()) { + PutModule("That note already exists. Use MOD to overwrite."); + } else if (AddNote(sKey, sValue)) { + PutModule("Added note [" + sKey + "]"); + } else { + PutModule("Unable to add note [" + sKey + "]"); + } + } else if (sCmd.Equals("MOD")) { + CString sKey(sLine.Token(1)); + CString sValue(sLine.Token(2, true)); + + if (AddNote(sKey, sValue)) { + PutModule("Set note for [" + sKey + "]"); + } else { + PutModule("Unable to add note [" + sKey + "]"); + } + } else if (sCmd.Equals("DEL")) { + CString sKey(sLine.Token(1)); + + if (DelNote(sKey)) { + PutModule("Deleted note [" + sKey + "]"); + } else { + PutModule("Unable to delete note [" + sKey + "]"); + } + } else { + PutModule("Commands are: Help, List, Add , Del , Mod "); + } + } + + virtual bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) { + if (sPageName.empty() || sPageName == "index") { + for (MCString::iterator it = BeginNV(); it != EndNV(); ++it) { + CTemplate& Row = Tmpl.AddRow("NotesLoop"); + + Row["Key"] = it->first; + Row["Note"] = it->second; + } + + return true; + } else if (sPageName == "delnote") { + DelNote(WebSock.GetParam("key")); + WebSock.Redirect("/mods/notes/"); + return true; + } else if (sPageName == "addnote") { + AddNote(WebSock.GetParam("key"), WebSock.GetParam("note")); + WebSock.Redirect("/mods/notes/"); + return true; + } + + return false; + } + +private: + map m_suSwitchCounters; +}; + +MODULEDEFS(CNotesMod, "Keep and replay notes") diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp index 30421516..2c109f83 100644 --- a/modules/webadmin.cpp +++ b/modules/webadmin.cpp @@ -12,41 +12,15 @@ #include "Template.h" #include "User.h" #include "znc.h" +#include "WebModules.h" +#include "ZNCString.h" #include +#include using std::stringstream; +using std::make_pair; -class CWebAdminMod; -class CWebAdminSock; - -class CWebAdminAuth : public CAuthBase { -public: - CWebAdminAuth(CWebAdminSock* pWebAdminSock, const CString& sUsername, const CString& sPassword); - - virtual ~CWebAdminAuth() {} - - void Invalidate() { m_pWebAdminSock = NULL; CAuthBase::Invalidate(); } - void AcceptedLogin(CUser& User); - void RefusedLogin(const CString& sReason); -private: -protected: - CWebAdminSock* m_pWebAdminSock; -}; - - -class CWebAdminSock : public CHTTPSock { -public: - CWebAdminSock(CWebAdminMod* pModule); - CWebAdminSock(CWebAdminMod* pModule, const CString& sHostname, unsigned short uPort, int iTimeout = 60); - virtual ~CWebAdminSock(); - - virtual bool OnPageRequest(const CString& sURI, CString& sPageRet); - virtual bool OnLogin(const CString& sUser, const CString& sPass); - - CString GetAvailSkinsDir(); - CString GetSkinDir(); - void PrintPage(CString& sPageRet, const CString& sTmplName); - +/* void GetErrorPage(CString& sPageRet, const CString& sError) { m_Template["Action"] = "error"; m_Template["Title"] = "Error"; @@ -54,20 +28,35 @@ public: PrintPage(sPageRet, "Error.tmpl"); } +*/ - void ListUsersPage(CString& sPageRet); - bool SettingsPage(CString& sPageRet); - bool ChanPage(CString& sPageRet, CChan* = NULL); - bool DelChan(CString& sPageRet); - bool UserPage(CString& sPageRet, CUser* pUser = NULL); - CUser* GetNewUser(CString& sPageRet, CUser* pUser); +class CWebAdminMod : public CGlobalModule { +public: + GLOBALMODCONSTRUCTOR(CWebAdminMod) { + AddSubPage(new CWebSubPage("settings", "Global Settings")); - CString GetModArgs(const CString& sModName, bool bGlobal = false) { - if (!bGlobal && !m_pUser) { + VPair vParams; + vParams.push_back(make_pair("user", "")); + AddSubPage(new CWebSubPage("edituser", "Your Settings", vParams)); + AddSubPage(new CWebSubPage("listusers", "List Users")); + AddSubPage(new CWebSubPage("adduser", "Add User")); + } + + virtual ~CWebAdminMod() { + } + + virtual bool OnLoad(const CString& sArgStr, CString& sMessage) { + return true; + } + + CString GetModArgs(CWebSock& WebSock, const CString& sModName, bool bGlobal = false) { + CUser* pUser = CZNC::Get().FindUser(WebSock.GetParam("user")); + + if (!bGlobal && !pUser) { return ""; } - CModules& Modules = (bGlobal) ? CZNC::Get().GetModules() : m_pUser->GetModules(); + CModules& Modules = (bGlobal) ? CZNC::Get().GetModules() : pUser->GetModules(); for (unsigned int a = 0; a < Modules.size(); a++) { CModule* pModule = Modules[a]; @@ -80,1094 +69,750 @@ public: return ""; } - // Setters - void SetSessionUser(CUser* p) { - m_pSessionUser = p; - m_bAdmin = p->IsAdmin(); + CUser* GetNewUser(CWebSock& WebSock, CUser* pUser) { + CString sUsername = WebSock.GetParam("newuser"); - // If m_pUser is not NULL, only that user can be edited. - if (m_bAdmin) { - m_pUser = NULL; - } else { - m_pUser = m_pSessionUser; - } - } - // !Setters - - virtual Csock* GetSockObj(const CString& sHost, unsigned short uPort); - bool IsAdmin() const { return m_bAdmin; } - - CWebAdminMod* GetModule() const { return (CWebAdminMod*) m_pModule; } - -private: -protected: - CUser* m_pUser; - CUser* m_pSessionUser; - bool m_bAdmin; - CTemplate m_Template; - CSmartPtr m_spAuth; -}; - -class CWebAdminMod : public CGlobalModule { -public: - GLOBALMODCONSTRUCTOR(CWebAdminMod) { - m_sSkinName = GetNV("SkinName"); - } - - virtual ~CWebAdminMod() { - } - - virtual bool OnLoad(const CString& sArgStr, CString& sMessage) { - bool bSSL = false; - bool bIPv6 = false; - unsigned short uPort = 8080; - CString sArgs(sArgStr); - CString sPort; - CString sListenHost; - - m_bShareIRCPorts = true; - - 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")) { - m_bShareIRCPorts = false; - } else { - sMessage = "Unknown option [" + sOpt + "] valid options are -ipv4, -ipv6 or -noircport"; - return false; - } + if (sUsername.empty()) { + sUsername = WebSock.GetParam("user"); } - // No arguments left: Only port sharing - if (sArgs.empty() && m_bShareIRCPorts) - return true; - - if (sArgs.find(" ") != CString::npos) { - sListenHost = sArgs.Token(0); - sPort = sArgs.Token(1, true); - } else { - sPort = sArgs; + if (sUsername.empty()) { + WebSock.PrintErrorPage("Invalid Submission [Username is required]"); + return NULL; } - if (sPort.Left(1) == "+") { -#ifdef HAVE_LIBSSL - sPort.TrimLeft("+"); - bSSL = true; -#else - return false; -#endif + if (pUser) { + /* If we are editing a user we must not change the user name */ + sUsername = pUser->GetUserName(); } - if (!sPort.empty()) { - uPort = sPort.ToUShort(); + CString sArg = WebSock.GetParam("password"); + + if (sArg != WebSock.GetParam("password2")) { + WebSock.PrintErrorPage("Invalid Submission [Passwords do not match]"); + return NULL; } - return OpenListener(sMessage, uPort, sListenHost, bSSL, bIPv6); - } + CUser* pNewUser = new CUser(sUsername); - bool OpenListener(CString &sMessage, u_short uPort, const CString& sListenHost, - bool bSSL = false, bool bIPv6 = false) { - CWebAdminSock* pListenSock = new CWebAdminSock(this); -#ifdef HAVE_LIBSSL - if (bSSL) { - pListenSock->SetPemLocation(CZNC::Get().GetPemLocation()); - } -#endif - - errno = 0; - bool b = m_pManager->ListenHost(uPort, "WebAdmin::Listener", sListenHost, bSSL, SOMAXCONN, pListenSock, 0, bIPv6); - if (!b) { - sMessage = "Could not bind to port " + CString(uPort); - if (!sListenHost.empty()) - sMessage += " on vhost [" + sListenHost + "]"; - if (errno != 0) - sMessage += ": " + CString(strerror(errno)); - } - return b; - } - - void SetSkinName(const CString& s) { - m_sSkinName = s; - SetNV("SkinName", m_sSkinName); - } - - CString GetSkinName() const { return (m_sSkinName.empty()) ? CString("default") : m_sSkinName; } - unsigned int GetSwitchCounter(const CString& sToken) { - map::iterator it = m_suSwitchCounters.find(sToken); - - if (it == m_suSwitchCounters.end()) { - m_suSwitchCounters[sToken] = 1; - return 1; + if (!sArg.empty()) { + CString sSalt = CUtils::GetSalt(); + CString sHash = CUser::SaltedHash(sArg, sSalt); + pNewUser->SetPass(sHash, CUser::HASH_DEFAULT, sSalt); } - return it->second; - } + VCString vsArgs; + WebSock.GetRawParam("servers").Split("\n", vsArgs); + unsigned int a = 0; - unsigned int IncSwitchCounter(const CString& sToken) { - map::iterator it = m_suSwitchCounters.find(sToken); - - if (it == m_suSwitchCounters.end()) { - m_suSwitchCounters[sToken] = 2; - return 2; + for (a = 0; a < vsArgs.size(); a++) { + pNewUser->AddServer(vsArgs[a].Trim_n()); } - return ++it->second; - } - - virtual EModRet OnUnknownUserRaw(CClient* pClient, CString& sLine) { - if (!m_bShareIRCPorts) - return CONTINUE; - - // If this is a HTTP request, we should handle it - if (sLine.WildCmp("GET * HTTP/1.?") - || sLine.WildCmp("POST * HTTP/1.?")) { - CWebAdminSock* pSock = new CWebAdminSock(this); - CZNC::Get().GetManager().SwapSockByAddr(pSock, pClient); - - // And don't forget to give it some sane settings again - pSock->SetSockName("WebAdmin::Client"); - pSock->SetTimeout(120); - pSock->SetMaxBufferThreshold(10240); - - // TODO can we somehow get rid of this? - pSock->ReadLine(sLine); - pSock->PushBuff("", 0, true); - - return HALT; - } - return CONTINUE; - } - -private: - CString m_sSkinName; - bool m_bShareIRCPorts; - map m_suSwitchCounters; -}; - -CString CWebAdminSock::GetAvailSkinsDir() { - return m_pModule->GetModDataDir() + "/skins/"; -} - -CString CWebAdminSock::GetSkinDir() { - CString sAvailSkins = GetAvailSkinsDir(); - CString sSkinDir = GetModule()->GetSkinName() + "/"; - CString sDir = CDir::CheckPathPrefix(sAvailSkins, sSkinDir, "/"); - - // Via CheckPrefix() we check if someone tries to use e.g. a skin name - // with embed .. or such evilness. - if (!sDir.empty() && CFile::IsDir(sDir)) { - return sDir + "/"; - } - - return m_pModule->GetModDataDir() + "/skins/default/"; -} - -void CWebAdminSock::PrintPage(CString& sPageRet, const CString& sTmplName) { - sPageRet.clear(); - CString sTmpl; - - if (IsAdmin()) { - sTmpl = GetSkinDir(); - } - - sTmpl += sTmplName; - - if (!m_Template.SetFile(GetSkinDir() + sTmplName)) { - sPageRet = CHTTPSock::GetErrorPage(404, "Not Found", "The template for this page [" + sTmpl + "] (or a template that it includes) was not found."); - return; - } - - stringstream oStr; - if (!m_Template.Print(oStr)) { - sPageRet = CHTTPSock::GetErrorPage(403, "Forbidden", "The template for this page [" + sTmpl + "] (or a template that it includes) can not be opened."); - return; - } - - sPageRet = oStr.str(); -} - -bool CWebAdminSock::OnLogin(const CString& sUser, const CString& sPass) { - m_spAuth = new CWebAdminAuth(this, sUser, sPass); - - // Some authentication module could need some time, block this socket - // until then. CWebAdminAuth will UnPauseRead(). - PauseRead(); - CZNC::Get().AuthUser(m_spAuth); - - // If CWebAdminAuth already set this, don't change it. - return IsLoggedIn(); -} - -void CWebAdminSock::ListUsersPage(CString& sPageRet) { - const map& msUsers = CZNC::Get().GetUserMap(); - m_Template["Title"] = "List Users"; - m_Template["Action"] = "listusers"; - - unsigned int a = 0; - - for (map::const_iterator it = msUsers.begin(); it != msUsers.end(); it++, a++) { - CServer* pServer = it->second->GetCurrentServer(); - CTemplate& l = m_Template.AddRow("UserLoop"); - CUser& User = *it->second; - - l["Username"] = User.GetUserName(); - l["Clients"] = CString(User.GetClients().size()); - l["IRCNick"] = User.GetIRCNick().GetNick(); - - if (&User == m_pSessionUser) { - l["IsSelf"] = "true"; - } - - if (pServer) { - l["Server"] = pServer->GetName(); - } - } - - PrintPage(sPageRet, "ListUsers.tmpl"); -} - -Csock* CWebAdminSock::GetSockObj(const CString& sHost, unsigned short uPort) { - CWebAdminSock* pSock = new CWebAdminSock(GetModule(), sHost, uPort); - pSock->SetSockName("WebAdmin::Client"); - pSock->SetTimeout(120); - - return pSock; -} - -CWebAdminSock::CWebAdminSock(CWebAdminMod* pModule) : CHTTPSock(pModule) { - m_pModule = pModule; - m_pUser = NULL; - m_pSessionUser = NULL; - m_bAdmin = false; - SetDocRoot(GetSkinDir()); -} - -CWebAdminSock::CWebAdminSock(CWebAdminMod* pModule, const CString& sHostname, unsigned short uPort, int iTimeout) - : CHTTPSock(pModule, sHostname, uPort, iTimeout) { - m_pModule = pModule; - m_pUser = NULL; - m_pSessionUser = NULL; - m_bAdmin = false; - SetDocRoot(GetSkinDir()); -} - -CWebAdminSock::~CWebAdminSock() { - if (!m_spAuth.IsNull()) { - CWebAdminAuth* pAuth = (CWebAdminAuth*) &(*m_spAuth); - pAuth->Invalidate(); - } -} - -bool CWebAdminSock::OnPageRequest(const CString& sURI, CString& sPageRet) { - if (!ForceLogin()) { - return false; - } - - const CString& sSkin = GetModule()->GetSkinName(); - CString sTmp = sURI; - - m_Template["SessionUser"] = GetUser(); - m_Template["SessionIP"] = GetRemoteIP(); - m_Template["Tag"] = CZNC::GetTag(); - m_Template["Skin"] = sSkin; - - if (IsAdmin()) { - m_Template["IsAdmin"] = "true"; - } - - if (sURI == "/") { - if (!IsAdmin()) { - Redirect("/edituser"); - return false; - } - - m_Template["Title"] = "Main Page"; - m_Template["Action"] = "home"; - PrintPage(sPageRet, "Main.tmpl"); - } else if (sTmp.TrimPrefix("/" + sSkin + "/")) { - SetDocRoot(GetSkinDir() + "/data"); - PrintFile(sTmp); - return false; - } else if (sURI == "/favicon.ico") { - PrintFile("/data/favicon.ico", "image/x-icon"); - return false; - } else if (sURI == "/home") { - m_Template["Title"] = "Main Page"; - m_Template["Action"] = "home"; - PrintPage(sPageRet, "Main.tmpl"); - } else if (sURI == "/settings") { - if (!IsAdmin()) { - return false; - } - - if (!SettingsPage(sPageRet)) { - return false; - } - } else if (sURI == "/switchuser") { - unsigned int uCurCnt = GetParam("cnt").ToUInt(); - unsigned int uCounter = GetModule()->GetSwitchCounter(GetRemoteIP()); - - if (!uCurCnt) { - Redirect("/switchuser?cnt=" + CString(uCounter)); - return false; - } - - if (uCurCnt >= uCounter) { - m_bLoggedIn = false; - GetModule()->IncSwitchCounter(GetRemoteIP()); - ForceLogin(); - } else { - Redirect("/home"); - } - - return false; - } else if (sURI == "/adduser") { - if (!IsAdmin()) { - return false; - } - - if (!UserPage(sPageRet)) { - return false; - } - } else if (sURI == "/edituser") { - if (!m_pUser) { - m_pUser = CZNC::Get().FindUser(GetParam("user")); - } - - if (m_pUser) { - if (!UserPage(sPageRet, m_pUser)) { - return false; + WebSock.GetRawParam("allowedips").Split("\n", vsArgs); + if (vsArgs.size()) { + for (a = 0; a < vsArgs.size(); a++) { + pNewUser->AddAllowedHost(vsArgs[a].Trim_n()); } } else { - GetErrorPage(sPageRet, "No such username"); - } - } else if (sURI == "/editchan") { - if (!m_pUser) { - m_pUser = CZNC::Get().FindUser(GetParam("user")); + pNewUser->AddAllowedHost("*"); } - if (!m_pUser) { - GetErrorPage(sPageRet, "No such username"); - return true; + WebSock.GetRawParam("ctcpreplies").Split("\n", vsArgs); + for (a = 0; a < vsArgs.size(); a++) { + CString sReply = vsArgs[a].TrimRight_n("\r"); + pNewUser->AddCTCPReply(sReply.Token(0).Trim_n(), sReply.Token(1, true).Trim_n()); } - CChan* pChan = m_pUser->FindChan(GetParam("name")); - if (!pChan) { - GetErrorPage(sPageRet, "No such channel"); - return true; - } + 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("skin"); if (!sArg.empty()) { pNewUser->SetSkinName(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); } - if (!ChanPage(sPageRet, pChan)) { - return false; - } - } else if (sURI == "/addchan") { - if (!m_pUser) { - m_pUser = CZNC::Get().FindUser(GetParam("user")); - } - - if (m_pUser) { - if (!ChanPage(sPageRet)) { - return false; + sArg = WebSock.GetParam("vhost"); + // To change VHosts be admin or don't have DenySetVHost + if (WebSock.IsAdmin() || !WebSock.GetSessionUser()->DenySetVHost()) { + if (!sArg.empty()) { + pNewUser->SetVHost(sArg); } - } else { - GetErrorPage(sPageRet, "No such username"); - } - } else if (sURI == "/delchan") { - if (!m_pUser) { - m_pUser = CZNC::Get().FindUser(GetParam("user")); + } else if (pUser){ + pNewUser->SetVHost(pUser->GetVHost()); } - if (m_pUser) { - if (!DelChan(sPageRet)) { - return false; - } - } else { - GetErrorPage(sPageRet, "No such username"); - } - } else if (sURI == "/listusers") { - if (!IsAdmin()) { - return false; + pNewUser->SetSkinName(WebSock.GetParam("skin")); + pNewUser->SetBufferCount(WebSock.GetParam("bufsize").ToUInt()); + pNewUser->SetKeepBuffer(WebSock.GetParam("keepbuffer").ToBool()); + pNewUser->SetMultiClients(WebSock.GetParam("multiclients").ToBool()); + pNewUser->SetBounceDCCs(WebSock.GetParam("bouncedccs").ToBool()); + pNewUser->SetUseClientIP(WebSock.GetParam("useclientip").ToBool()); + pNewUser->SetTimestampAppend(WebSock.GetParam("appendtimestamp").ToBool()); + pNewUser->SetTimestampPrepend(WebSock.GetParam("prependtimestamp").ToBool()); + pNewUser->SetTimezoneOffset(WebSock.GetParam("timezoneoffset").ToDouble()); + pNewUser->SetJoinTries(WebSock.GetParam("jointries").ToUInt()); + pNewUser->SetMaxJoins(WebSock.GetParam("maxjoins").ToUInt()); + + if (WebSock.IsAdmin()) { + pNewUser->SetDenyLoadMod(WebSock.GetParam("denyloadmod").ToBool()); + pNewUser->SetDenySetVHost(WebSock.GetParam("denysetvhost").ToBool()); + } else if (pUser) { + pNewUser->SetDenyLoadMod(pUser->DenyLoadMod()); + pNewUser->SetDenySetVHost(pUser->DenySetVHost()); } - ListUsersPage(sPageRet); - } else if (sURI == "/deluser") { - if (!IsAdmin()) { - return false; + // If pUser is not NULL, 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()); } - CString sUser = GetParam("user"); - CUser* pUser = CZNC::Get().FindUser(sUser); - - if (pUser && pUser == m_pSessionUser) { - GetErrorPage(sPageRet, "You are not allowed to delete yourself"); - } else if (CZNC::Get().DeleteUser(sUser)) { - Redirect("/listusers"); - return false; - } else { - GetErrorPage(sPageRet, "No such username"); - } - } else { - return false; - } - - return true; -} - -bool CWebAdminSock::SettingsPage(CString& sPageRet) { - if (!GetParam("submitted").ToUInt()) { - CString sVHosts, sMotd; - m_Template["Action"] = "settings"; - m_Template["Title"] = "Settings"; - m_Template["StatusPrefix"] = CZNC::Get().GetStatusPrefix(); - m_Template["ISpoofFile"] = CZNC::Get().GetISpoofFile(); - m_Template["ISpoofFormat"] = CZNC::Get().GetISpoofFormat(); - - const VCString& vsVHosts = CZNC::Get().GetVHosts(); - for (unsigned int a = 0; a < vsVHosts.size(); a++) { - CTemplate& l = m_Template.AddRow("VHostLoop"); - l["VHost"] = vsVHosts[a]; + WebSock.GetParamValues("channel", vsArgs); + for (a = 0; a < vsArgs.size(); a++) { + const CString& sChan = vsArgs[a]; + pNewUser->AddChan(sChan.TrimRight_n("\r"), WebSock.GetParam("save_" + sChan).ToBool()); } - const VCString& vsMotd = CZNC::Get().GetMotd(); - for (unsigned int b = 0; b < vsMotd.size(); b++) { - CTemplate& l = m_Template.AddRow("MOTDLoop"); - l["Line"] = vsMotd[b]; - } + if (WebSock.IsAdmin() || (pUser && !pUser->DenyLoadMod())) { + WebSock.GetParamValues("loadmod", vsArgs); - const vector& vpListeners = CZNC::Get().GetListeners(); - for (unsigned int c = 0; c < vpListeners.size(); c++) { - CListener* pListener = vpListeners[c]; - CTemplate& l = m_Template.AddRow("ListenLoop"); + for (a = 0; a < vsArgs.size(); a++) { + CString sModRet; + CString sModName = vsArgs[a].TrimRight_n("\r"); - l["Port"] = CString(pListener->GetPort()); - l["BindHost"] = pListener->GetBindHost(); + if (!sModName.empty()) { + CString sArgs = WebSock.GetParam("modargs_" + sModName); -#ifdef HAVE_LIBSSL - if (pListener->IsSSL()) { - l["IsSSL"] = "true"; - } -#endif - -#ifdef HAVE_IPV6 - if (pListener->IsIPV6()) { - l["IsIPV6"] = "true"; - } -#endif - } - - CString sDir(GetAvailSkinsDir()); - - if (CFile::IsDir(sDir)) { - CDir Dir(sDir); - - m_Template.AddRow("SkinLoop")["Name"] = "default"; - - for (unsigned int d = 0; d < Dir.size(); d++) { - const CFile& SubDir = *Dir[d]; - - if (SubDir.IsDir() && - SubDir.GetShortName() != ".svn" && - SubDir.GetShortName() != "default") { - CTemplate& l = m_Template.AddRow("SkinLoop"); - l["Name"] = SubDir.GetShortName(); - - if (SubDir.GetShortName() == GetModule()->GetSkinName()) { - l["Checked"] = "true"; + try { + if (!pNewUser->GetModules().LoadModule(sModName, sArgs, pNewUser, sModRet, (pUser != NULL))) { + DEBUG("Unable to load module [" << sModName << "] [" << sModRet << "]"); + } + } catch (...) { + DEBUG("Unable to load module [" << sModName << "] [" << sArgs << "]"); } } } - } + } else if (pUser) { + CModules& Modules = pUser->GetModules(); - set ssGlobalMods; - CZNC::Get().GetModules().GetAvailableMods(ssGlobalMods, true); - - for (set::iterator it = ssGlobalMods.begin(); it != ssGlobalMods.end(); ++it) { - const CModInfo& Info = *it; - CTemplate& l = m_Template.AddRow("ModuleLoop"); - - if (CZNC::Get().GetModules().FindModule(Info.GetName())) { - l["Checked"] = "true"; - } - - if (Info.GetName() == m_pModule->GetModName()) { - l["Disabled"] = "true"; - } - - l["Name"] = Info.GetName(); - l["Description"] = Info.GetDescription(); - l["Args"] = GetModArgs(Info.GetName(), true); - } - - PrintPage(sPageRet, "Settings.tmpl"); - return true; - } - - CString sArg; - sArg = GetParam("statusprefix"); CZNC::Get().SetStatusPrefix(sArg); - sArg = GetParam("ispooffile"); CZNC::Get().SetISpoofFile(sArg); - sArg = GetParam("ispoofformat"); CZNC::Get().SetISpoofFormat(sArg); - //sArg = GetParam(""); if (!sArg.empty()) { CZNC::Get().Set(sArg); } - - VCString vsArgs; - GetRawParam("motd").Split("\n", vsArgs); - CZNC::Get().ClearMotd(); - - unsigned int a = 0; - for (a = 0; a < vsArgs.size(); a++) { - CZNC::Get().AddMotd(vsArgs[a].TrimRight_n()); - } - - GetRawParam("vhosts").Split("\n", vsArgs); - CZNC::Get().ClearVHosts(); - - for (a = 0; a < vsArgs.size(); a++) { - CZNC::Get().AddVHost(vsArgs[a].Trim_n()); - } - - GetModule()->SetSkinName(GetParam("skin")); - - set ssArgs; - GetParamValues("loadmod", ssArgs); - - for (set::iterator it = ssArgs.begin(); it != ssArgs.end(); ++it) { - CString sModRet; - CString sModName = (*it).TrimRight_n("\r"); - - if (!sModName.empty()) { - CString sArgs = GetParam("modargs_" + sModName); - - CModule *pMod = CZNC::Get().GetModules().FindModule(sModName); - if (!pMod) { - if (!CZNC::Get().GetModules().LoadModule(sModName, sArgs, NULL, sModRet)) { - DEBUG("Unable to load module [" << sModName << "] [" << sModRet << "]"); - } - } else if (pMod->GetArgs() != sArgs) { - if (!CZNC::Get().GetModules().ReloadModule(sModName, sArgs, NULL, sModRet)) { - DEBUG("Unable to reload module [" << sModName << "] [" << sModRet << "]"); - } - } else { - DEBUG("Unable to load module [" << sModName << "] because it is already loaded"); - } - } - } - - const CModules& vCurMods = CZNC::Get().GetModules(); - set ssUnloadMods; - - for (a = 0; a < vCurMods.size(); a++) { - CModule* pCurMod = vCurMods[a]; - - if (ssArgs.find(pCurMod->GetModName()) == ssArgs.end() && pCurMod->GetModName() != m_pModule->GetModName()) { - ssUnloadMods.insert(pCurMod->GetModName()); - } - } - - for (set::iterator it2 = ssUnloadMods.begin(); it2 != ssUnloadMods.end(); ++it2) { - CZNC::Get().GetModules().UnloadModule(*it2); - } - - if (!CZNC::Get().WriteConfig()) { - GetErrorPage(sPageRet, "Settings changed, but config was not written"); - return true; - } - - Redirect("/settings"); - return false; -} - -bool CWebAdminSock::ChanPage(CString& sPageRet, CChan* pChan) { - if (!m_pUser) { - GetErrorPage(sPageRet, "That user doesn't exist"); - return true; - } - - if (!GetParam("submitted").ToUInt()) { - m_Template["User"] = m_pUser->GetUserName(); - - if (pChan) { - m_Template["Action"] = "editchan"; - m_Template["Edit"] = "true"; - m_Template["Title"] = "Edit Channel" + CString(" [" + pChan->GetName() + "]"); - m_Template["ChanName"] = pChan->GetName(); - m_Template["BufferCount"] = CString(pChan->GetBufferCount()); - m_Template["DefModes"] = pChan->GetDefaultModes(); - - if (pChan->InConfig()) { - m_Template["InConfig"] = "true"; - } - } else { - m_Template["Action"] = "addchan"; - m_Template["Title"] = "Add Channel" + CString(" for User [" + m_pUser->GetUserName() + "]"); - m_Template["BufferCount"] = CString(m_pUser->GetBufferCount()); - m_Template["DefModes"] = CString(m_pUser->GetDefaultChanModes()); - m_Template["InConfig"] = "true"; - } - - /* o1 used to be AutoCycle which was removed */ - - CTemplate& o2 = m_Template.AddRow("OptionLoop"); - o2["Name"] = "keepbuffer"; - o2["DisplayName"] = "Keep Buffer"; - if ((pChan && pChan->KeepBuffer()) || (!pChan && m_pUser->KeepBuffer())) { o2["Checked"] = "true"; } - - CTemplate& o3 = m_Template.AddRow("OptionLoop"); - o3["Name"] = "detached"; - o3["DisplayName"] = "Detached"; - if (pChan && pChan->IsDetached()) { o3["Checked"] = "true"; } - - PrintPage(sPageRet, "Channel.tmpl"); - return true; - } - - CString sChanName = GetParam("name").Trim_n(); - - if (!pChan) { - if (sChanName.empty()) { - GetErrorPage(sPageRet, "Channel name is a required argument"); - return true; - } - - if (m_pUser->FindChan(sChanName.Token(0))) { - GetErrorPage(sPageRet, "Channel [" + sChanName.Token(0) + "] already exists"); - return true; - } - - pChan = new CChan(sChanName, m_pUser, true); - m_pUser->AddChan(pChan); - } - - pChan->SetDefaultModes(GetParam("defmodes")); - pChan->SetBufferCount(GetParam("buffercount").ToUInt()); - pChan->SetInConfig(GetParam("save").ToBool()); - pChan->SetKeepBuffer(GetParam("keepbuffer").ToBool()); - - bool bDetached = GetParam("detached").ToBool(); - - if (pChan->IsDetached() != bDetached) { - if (bDetached) { - pChan->DetachUser(); - } else { - pChan->AttachUser(); - } - } - - if (!CZNC::Get().WriteConfig()) { - GetErrorPage(sPageRet, "Channel added/modified, but config was not written"); - return true; - } - - Redirect("/edituser?user=" + m_pUser->GetUserName().Escape_n(CString::EURL)); - return false; -} - -bool CWebAdminSock::DelChan(CString& sPageRet) { - CString sChan = GetParam("name"); - - if (!m_pUser) { - GetErrorPage(sPageRet, "That user doesn't exist"); - return true; - } - - if (sChan.empty()) { - GetErrorPage(sPageRet, "That channel doesn't exist for this user"); - return true; - } - - m_pUser->DelChan(sChan); - m_pUser->PutIRC("PART " + sChan); - - if (!CZNC::Get().WriteConfig()) { - GetErrorPage(sPageRet, "Channel deleted, but config was not written"); - return true; - } - - Redirect("/edituser?user=" + m_pUser->GetUserName().Escape_n(CString::EURL)); - return false; -} - -bool CWebAdminSock::UserPage(CString& sPageRet, CUser* pUser) { - if (!GetParam("submitted").ToUInt()) { - CString sAllowedHosts, sServers, sChans, sCTCPReplies; - - if (pUser) { - m_Template["Action"] = "edituser"; - m_Template["Title"] = "Edit User [" + pUser->GetUserName() + "]"; - m_Template["Edit"] = "true"; - m_Template["Username"] = pUser->GetUserName(); - m_Template["Nick"] = pUser->GetNick(); - m_Template["AltNick"] = pUser->GetAltNick(); - m_Template["StatusPrefix"] = pUser->GetStatusPrefix(); - m_Template["Ident"] = pUser->GetIdent(); - m_Template["RealName"] = pUser->GetRealName(); - m_Template["QuitMsg"] = pUser->GetQuitMsg(); - m_Template["DefaultChanModes"] = pUser->GetDefaultChanModes(); - m_Template["BufferCount"] = CString(pUser->GetBufferCount()); - m_Template["TimestampFormat"] = pUser->GetTimestampFormat(); - m_Template["TimezoneOffset"] = CString(pUser->GetTimezoneOffset()); - m_Template["JoinTries"] = CString(pUser->JoinTries()); - m_Template["MaxJoins"] = CString(pUser->MaxJoins()); - - const set& ssAllowedHosts = pUser->GetAllowedHosts(); - for (set::const_iterator it = ssAllowedHosts.begin(); it != ssAllowedHosts.end(); ++it) { - CTemplate& l = m_Template.AddRow("AllowedHostLoop"); - l["Host"] = *it; - } - - const vector& vServers = pUser->GetServers(); - for (unsigned int a = 0; a < vServers.size(); a++) { - CTemplate& l = m_Template.AddRow("ServerLoop"); - l["Server"] = vServers[a]->GetString(); - } - - const MCString& msCTCPReplies = pUser->GetCTCPReplies(); - for (MCString::const_iterator it2 = msCTCPReplies.begin(); it2 != msCTCPReplies.end(); ++it2) { - CTemplate& l = m_Template.AddRow("CTCPLoop"); - l["CTCP"] = it2->first + " " + it2->second; - } - - const vector& Channels = pUser->GetChans(); - for (unsigned int c = 0; c < Channels.size(); c++) { - CChan* pChan = Channels[c]; - CTemplate& l = m_Template.AddRow("ChannelLoop"); - - l["Username"] = pUser->GetUserName(); - l["Name"] = pChan->GetName(); - l["Perms"] = pChan->GetPermStr(); - l["CurModes"] = pChan->GetModeString(); - l["DefModes"] = pChan->GetDefaultModes(); - l["BufferCount"] = CString(pChan->GetBufferCount()); - l["Options"] = pChan->GetOptions(); - - if (pChan->InConfig()) { - l["InConfig"] = "true"; - } - } - } else { - m_Template["Action"] = "adduser"; - m_Template["Title"] = "Add User"; - m_Template["StatusPrefix"] = "*"; - } - - // To change VHosts be admin or don't have DenySetVHost - const VCString& vsVHosts = CZNC::Get().GetVHosts(); - bool bFoundVHost = false; - if (IsAdmin() || !m_pSessionUser->DenySetVHost()) { - for (unsigned int b = 0; b < vsVHosts.size(); b++) { - const CString& sVHost = vsVHosts[b]; - CTemplate& l = m_Template.AddRow("VHostLoop"); - - l["VHost"] = sVHost; - - if (pUser && pUser->GetVHost() == sVHost) { - l["Checked"] = "true"; - bFoundVHost = true; - } - } - - // If our current vhost is not in the global list... - if (pUser && !bFoundVHost && !pUser->GetVHost().empty()) { - CTemplate& l = m_Template.AddRow("VHostLoop"); - - l["VHost"] = pUser->GetVHost(); - l["Checked"] = "true"; - } - } - - set ssUserMods; - CZNC::Get().GetModules().GetAvailableMods(ssUserMods); - - for (set::iterator it = ssUserMods.begin(); it != ssUserMods.end(); ++it) { - const CModInfo& Info = *it; - CTemplate& l = m_Template.AddRow("ModuleLoop"); - - l["Name"] = Info.GetName(); - l["Description"] = Info.GetDescription(); - l["Args"] = GetModArgs(Info.GetName()); - - if (pUser && pUser->GetModules().FindModule(Info.GetName())) { - l["Checked"] = "true"; - } - - if (!IsAdmin() && pUser && pUser->DenyLoadMod()) { - l["Disabled"] = "true"; - } - } - - CTemplate& o1 = m_Template.AddRow("OptionLoop"); - o1["Name"] = "keepbuffer"; - o1["DisplayName"] = "Keep Buffer"; - if (!pUser || pUser->KeepBuffer()) { o1["Checked"] = "true"; } - - /* o2 used to be auto cycle which was removed */ - - CTemplate& o4 = m_Template.AddRow("OptionLoop"); - o4["Name"] = "multiclients"; - o4["DisplayName"] = "Multi Clients"; - if (!pUser || pUser->MultiClients()) { o4["Checked"] = "true"; } - - CTemplate& o5 = m_Template.AddRow("OptionLoop"); - o5["Name"] = "bouncedccs"; - o5["DisplayName"] = "Bounce DCCs"; - if (!pUser || pUser->BounceDCCs()) { o5["Checked"] = "true"; } - - CTemplate& o6 = m_Template.AddRow("OptionLoop"); - o6["Name"] = "useclientip"; - o6["DisplayName"] = "Use Client IP"; - if (pUser && pUser->UseClientIP()) { o6["Checked"] = "true"; } - - CTemplate& o7 = m_Template.AddRow("OptionLoop"); - o7["Name"] = "appendtimestamp"; - o7["DisplayName"] = "Append Timestamps"; - if (pUser && pUser->GetTimestampAppend()) { o7["Checked"] = "true"; } - - CTemplate& o8 = m_Template.AddRow("OptionLoop"); - o8["Name"] = "prependtimestamp"; - o8["DisplayName"] = "Prepend Timestamps"; - if (pUser && pUser->GetTimestampPrepend()) { o8["Checked"] = "true"; } - - if (IsAdmin()) { - CTemplate& o9 = m_Template.AddRow("OptionLoop"); - o9["Name"] = "denyloadmod"; - o9["DisplayName"] = "Deny LoadMod"; - if (pUser && pUser->DenyLoadMod()) { o9["Checked"] = "true"; } - - CTemplate& o10 = m_Template.AddRow("OptionLoop"); - o10["Name"] = "isadmin"; - o10["DisplayName"] = "Admin"; - if (pUser && pUser->IsAdmin()) { o10["Checked"] = "true"; } - if (pUser && pUser == CZNC::Get().FindUser(GetUser())) { o10["Disabled"] = "true"; } - - CTemplate& o11 = m_Template.AddRow("OptionLoop"); - o11["Name"] = "denysetvhost"; - o11["DisplayName"] = "Deny SetVHost"; - if (pUser && pUser->DenySetVHost()) { o11["Checked"] = "true"; } - } - - PrintPage(sPageRet, "UserPage.tmpl"); - return true; - } - - /* If pUser is NULL, we are adding a user, else we are editing this one */ - - CString sUsername = GetParam("user"); - if (!pUser && CZNC::Get().FindUser(sUsername)) { - GetErrorPage(sPageRet, "Invalid Submission [User " + sUsername + " already exists]"); - return true; - } - - CUser* pNewUser = GetNewUser(sPageRet, pUser); - if (!pNewUser) { - return true; - } - - CString sErr; - - if (!pUser) { - // Add User Submission - if (!CZNC::Get().AddUser(pNewUser, sErr)) { - delete pNewUser; - GetErrorPage(sPageRet, "Invalid submission [" + sErr + "]"); - return true; - } - - if (!CZNC::Get().WriteConfig()) { - GetErrorPage(sPageRet, "User added, but config was not written"); - return true; - } - } else { - // Edit User Submission - if (!pUser->Clone(*pNewUser, sErr, false)) { - delete pNewUser; - GetErrorPage(sPageRet, "Invalid Submission [" + sErr + "]"); - return true; - } - - delete pNewUser; - if (!CZNC::Get().WriteConfig()) { - GetErrorPage(sPageRet, "User edited, but config was not written"); - return true; - } - } - - if (!IsAdmin()) { - Redirect("/edituser"); - } else { - Redirect("/listusers"); - } - - return false; -} - -CUser* CWebAdminSock::GetNewUser(CString& sPageRet, CUser* pUser) { - CString sUsername = GetParam("newuser"); - - if (sUsername.empty()) { - sUsername = GetParam("user"); - } - - if (sUsername.empty()) { - GetErrorPage(sPageRet, "Invalid Submission [Username is required]"); - return NULL; - } - - if (pUser) { - /* If we are editing a user we must not change the user name */ - sUsername = pUser->GetUserName(); - } - - CString sArg = GetParam("password"); - - if (sArg != GetParam("password2")) { - GetErrorPage(sPageRet, "Invalid Submission [Passwords do not match]"); - return NULL; - } - - 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; - GetRawParam("servers").Split("\n", vsArgs); - unsigned int a = 0; - - for (a = 0; a < vsArgs.size(); a++) { - pNewUser->AddServer(vsArgs[a].Trim_n()); - } - - GetRawParam("allowedips").Split("\n", vsArgs); - if (vsArgs.size()) { - for (a = 0; a < vsArgs.size(); a++) { - pNewUser->AddAllowedHost(vsArgs[a].Trim_n()); - } - } else { - pNewUser->AddAllowedHost("*"); - } - - GetRawParam("ctcpreplies").Split("\n", vsArgs); - for (a = 0; a < vsArgs.size(); a++) { - CString sReply = vsArgs[a].TrimRight_n("\r"); - pNewUser->AddCTCPReply(sReply.Token(0).Trim_n(), sReply.Token(1, true).Trim_n()); - } - - sArg = GetParam("nick"); if (!sArg.empty()) { pNewUser->SetNick(sArg); } - sArg = GetParam("altnick"); if (!sArg.empty()) { pNewUser->SetAltNick(sArg); } - sArg = GetParam("statusprefix"); if (!sArg.empty()) { pNewUser->SetStatusPrefix(sArg); } - sArg = GetParam("ident"); if (!sArg.empty()) { pNewUser->SetIdent(sArg); } - sArg = GetParam("realname"); if (!sArg.empty()) { pNewUser->SetRealName(sArg); } - sArg = GetParam("quitmsg"); if (!sArg.empty()) { pNewUser->SetQuitMsg(sArg); } - sArg = GetParam("chanmodes"); if (!sArg.empty()) { pNewUser->SetDefaultChanModes(sArg); } - sArg = GetParam("timestampformat"); if (!sArg.empty()) { pNewUser->SetTimestampFormat(sArg); } - - sArg = GetParam("vhost"); - // To change VHosts be admin or don't have DenySetVHost - if (IsAdmin() || !m_pSessionUser->DenySetVHost()) { - if (!sArg.empty()) - pNewUser->SetVHost(sArg); - } else if (pUser){ - pNewUser->SetVHost(pUser->GetVHost()); - } - - pNewUser->SetBufferCount(GetParam("bufsize").ToUInt()); - pNewUser->SetKeepBuffer(GetParam("keepbuffer").ToBool()); - pNewUser->SetMultiClients(GetParam("multiclients").ToBool()); - pNewUser->SetBounceDCCs(GetParam("bouncedccs").ToBool()); - pNewUser->SetUseClientIP(GetParam("useclientip").ToBool()); - pNewUser->SetTimestampAppend(GetParam("appendtimestamp").ToBool()); - pNewUser->SetTimestampPrepend(GetParam("prependtimestamp").ToBool()); - pNewUser->SetTimezoneOffset(GetParam("timezoneoffset").ToDouble()); - pNewUser->SetJoinTries(GetParam("jointries").ToUInt()); - pNewUser->SetMaxJoins(GetParam("maxjoins").ToUInt()); - - if (IsAdmin()) { - pNewUser->SetDenyLoadMod(GetParam("denyloadmod").ToBool()); - pNewUser->SetDenySetVHost(GetParam("denysetvhost").ToBool()); - } else if (pUser) { - pNewUser->SetDenyLoadMod(pUser->DenyLoadMod()); - pNewUser->SetDenySetVHost(pUser->DenySetVHost()); - } - - // If pUser is not NULL, we are editing an existing user. - // Users must not be able to change their own admin flag. - if (pUser != CZNC::Get().FindUser(GetUser())) { - pNewUser->SetAdmin(GetParam("isadmin").ToBool()); - } else if (pUser) { - pNewUser->SetAdmin(pUser->IsAdmin()); - } - - GetParamValues("channel", vsArgs); - for (a = 0; a < vsArgs.size(); a++) { - const CString& sChan = vsArgs[a]; - pNewUser->AddChan(sChan.TrimRight_n("\r"), GetParam("save_" + sChan).ToBool()); - } - - if (IsAdmin() || (pUser && !pUser->DenyLoadMod())) { - GetParamValues("loadmod", vsArgs); - - for (a = 0; a < vsArgs.size(); a++) { - CString sModRet; - CString sModName = vsArgs[a].TrimRight_n("\r"); - - if (!sModName.empty()) { - CString sArgs = GetParam("modargs_" + sModName); + for (a = 0; a < Modules.size(); a++) { + CString sModName = Modules[a]->GetModName(); + CString sArgs = Modules[a]->GetArgs(); + CString sModRet; try { if (!pNewUser->GetModules().LoadModule(sModName, sArgs, pNewUser, sModRet, (pUser != NULL))) { DEBUG("Unable to load module [" << sModName << "] [" << sModRet << "]"); } } catch (...) { - DEBUG("Unable to load module [" << sModName << "] [" << sArgs << "]"); + DEBUG("Unable to load module [" << sModName << "]"); } } } - } else if (pUser) { - CModules& Modules = pUser->GetModules(); - for (a = 0; a < Modules.size(); a++) { - CString sModName = Modules[a]->GetModName(); - CString sArgs = Modules[a]->GetArgs(); + return pNewUser; + } + + virtual bool WebRequiresLogin() { return true; } + virtual bool WebRequiresAdmin() { return false; } + virtual CString GetWebNavTitle() { return "webadmin"; } + virtual bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) { + std::cerr << "=============================== webadmin sPageName=[" << sPageName << "]" << std::endl; + + if (sPageName == "settings") { + return SettingsPage(WebSock, Tmpl); + } else if (sPageName == "adduser") { + return UserPage(WebSock, Tmpl); + } else if (sPageName == "editchan") { + CUser* pUser = CZNC::Get().FindUser(WebSock.GetParam("user")); + + if (!pUser) { + WebSock.PrintErrorPage("No such username"); + return true; + } + + CChan* pChan = pUser->FindChan(WebSock.GetParam("name")); + if (!pChan) { + WebSock.PrintErrorPage("No such channel"); + return true; + } + + return ChanPage(WebSock, Tmpl, pUser, pChan); + } else if (sPageName == "addchan") { + CUser* pUser = CZNC::Get().FindUser(WebSock.GetParam("user")); + + if (pUser) { + return ChanPage(WebSock, Tmpl, pUser); + } + + WebSock.PrintErrorPage("No such username"); + } else if (sPageName == "delchan") { + CUser* pUser = CZNC::Get().FindUser(WebSock.GetParam("user")); + + if (pUser) { + return DelChan(WebSock, pUser); + } + + WebSock.PrintErrorPage("No such username"); + } else if (sPageName == "deluser") { + if (!WebSock.IsAdmin()) { + WebSock.PrintErrorPage("You are not an admin"); + return true; + } + + CString sUser = WebSock.GetParam("user"); + CUser* pUser = CZNC::Get().FindUser(sUser); + + if (pUser && pUser == WebSock.GetSessionUser()) { + WebSock.PrintErrorPage("You are not allowed to delete yourself"); + return true; + } else if (CZNC::Get().DeleteUser(sUser)) { + WebSock.Redirect("listusers"); + return true; + } + + WebSock.PrintErrorPage("No such username"); + return true; + } else if (sPageName == "edituser") { + CUser* pUser = WebSock.HasParam("user") ? CZNC::Get().FindUser(WebSock.GetParam("user")) : WebSock.GetSessionUser(); + + if (pUser) { + return UserPage(WebSock, Tmpl, pUser); + } + + WebSock.PrintErrorPage("No such username"); + } else if (sPageName == "listusers") { + return ListUsersPage(WebSock, Tmpl); + } else if (sPageName.empty() || sPageName == "index") { + return true; + } + + return false; + } + + bool ChanPage(CWebSock& WebSock, CTemplate& Tmpl, CUser* pUser, CChan* pChan = NULL) { + Tmpl.SetFile("add_edit_chan.tmpl"); + + if (!pUser) { + WebSock.PrintErrorPage("That user doesn't exist"); + return true; + } + + if (!WebSock.GetParam("submitted").ToUInt()) { + Tmpl["User"] = pUser->GetUserName(); + + if (pChan) { + Tmpl["Action"] = "editchan"; + Tmpl["Edit"] = "true"; + Tmpl["Title"] = "Edit Channel" + CString(" [" + pChan->GetName() + "]"); + Tmpl["ChanName"] = pChan->GetName(); + Tmpl["BufferCount"] = CString(pChan->GetBufferCount()); + Tmpl["DefModes"] = pChan->GetDefaultModes(); + + if (pChan->InConfig()) { + Tmpl["InConfig"] = "true"; + } + } else { + Tmpl["Action"] = "addchan"; + Tmpl["Title"] = "Add Channel" + CString(" for User [" + pUser->GetUserName() + "]"); + Tmpl["BufferCount"] = CString(pUser->GetBufferCount()); + Tmpl["DefModes"] = CString(pUser->GetDefaultChanModes()); + Tmpl["InConfig"] = "true"; + } + + // o1 used to be AutoCycle which was removed + + CTemplate& o2 = Tmpl.AddRow("OptionLoop"); + o2["Name"] = "keepbuffer"; + o2["DisplayName"] = "Keep Buffer"; + if ((pChan && pChan->KeepBuffer()) || (!pChan && pUser->KeepBuffer())) { o2["Checked"] = "true"; } + + CTemplate& o3 = Tmpl.AddRow("OptionLoop"); + o3["Name"] = "detached"; + o3["DisplayName"] = "Detached"; + if (pChan && pChan->IsDetached()) { o3["Checked"] = "true"; } + + return true; + } + + CString sChanName = WebSock.GetParam("name").Trim_n(); + + if (!pChan) { + if (sChanName.empty()) { + WebSock.PrintErrorPage("Channel name is a required argument"); + return true; + } + + if (pUser->FindChan(sChanName.Token(0))) { + WebSock.PrintErrorPage("Channel [" + sChanName.Token(0) + "] already exists"); + return true; + } + + pChan = new CChan(sChanName, pUser, true); + pUser->AddChan(pChan); + } + + pChan->SetDefaultModes(WebSock.GetParam("defmodes")); + pChan->SetBufferCount(WebSock.GetParam("buffercount").ToUInt()); + pChan->SetInConfig(WebSock.GetParam("save").ToBool()); + pChan->SetKeepBuffer(WebSock.GetParam("keepbuffer").ToBool()); + + bool bDetached = WebSock.GetParam("detached").ToBool(); + + if (pChan->IsDetached() != bDetached) { + if (bDetached) { + pChan->DetachUser(); + } else { + pChan->AttachUser(); + } + } + + if (!CZNC::Get().WriteConfig()) { + WebSock.PrintErrorPage("Channel added/modified, but config was not written"); + return true; + } + + WebSock.Redirect("edituser?user=" + pUser->GetUserName().Escape_n(CString::EURL)); + return true; + } + + bool DelChan(CWebSock& WebSock, CUser* pUser) { + CString sChan = WebSock.GetParam("name"); + + if (!pUser) { + WebSock.PrintErrorPage("That user doesn't exist"); + return true; + } + + if (sChan.empty()) { + WebSock.PrintErrorPage("That channel doesn't exist for this user"); + return true; + } + + pUser->DelChan(sChan); + pUser->PutIRC("PART " + sChan); + + if (!CZNC::Get().WriteConfig()) { + WebSock.PrintErrorPage("Channel deleted, but config was not written"); + return true; + } + + WebSock.Redirect("edituser?user=" + pUser->GetUserName().Escape_n(CString::EURL)); + return false; + } + + bool UserPage(CWebSock& WebSock, CTemplate& Tmpl, CUser* pUser = NULL) { + Tmpl.SetFile("add_edit_user.tmpl"); + + if (!WebSock.GetParam("submitted").ToUInt()) { + CString sAllowedHosts, sServers, sChans, sCTCPReplies; + + if (pUser) { + Tmpl["Action"] = "edituser"; + Tmpl["Title"] = "Edit User [" + pUser->GetUserName() + "]"; + Tmpl["Edit"] = "true"; + 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["BufferCount"] = CString(pUser->GetBufferCount()); + Tmpl["TimestampFormat"] = pUser->GetTimestampFormat(); + Tmpl["TimezoneOffset"] = CString(pUser->GetTimezoneOffset()); + Tmpl["JoinTries"] = CString(pUser->JoinTries()); + Tmpl["MaxJoins"] = CString(pUser->MaxJoins()); + + const set& ssAllowedHosts = pUser->GetAllowedHosts(); + for (set::const_iterator it = ssAllowedHosts.begin(); it != ssAllowedHosts.end(); ++it) { + CTemplate& l = Tmpl.AddRow("AllowedHostLoop"); + l["Host"] = *it; + } + + const vector& vServers = pUser->GetServers(); + for (unsigned int a = 0; a < vServers.size(); a++) { + CTemplate& l = Tmpl.AddRow("ServerLoop"); + l["Server"] = vServers[a]->GetString(); + } + + const MCString& msCTCPReplies = pUser->GetCTCPReplies(); + for (MCString::const_iterator it2 = msCTCPReplies.begin(); it2 != msCTCPReplies.end(); it2++) { + CTemplate& l = Tmpl.AddRow("CTCPLoop"); + l["CTCP"] = it2->first + " " + it2->second; + } + + const vector& Channels = pUser->GetChans(); + for (unsigned int c = 0; c < Channels.size(); c++) { + CChan* pChan = Channels[c]; + CTemplate& l = Tmpl.AddRow("ChannelLoop"); + + l["Username"] = pUser->GetUserName(); + l["Name"] = pChan->GetName(); + l["Perms"] = pChan->GetPermStr(); + l["CurModes"] = pChan->GetModeString(); + l["DefModes"] = pChan->GetDefaultModes(); + l["BufferCount"] = CString(pChan->GetBufferCount()); + l["Options"] = pChan->GetOptions(); + + if (pChan->InConfig()) { + l["InConfig"] = "true"; + } + } + } else { + Tmpl["Action"] = "adduser"; + Tmpl["Title"] = "Add User"; + Tmpl["StatusPrefix"] = "*"; + } + + // To change VHosts be admin or don't have DenySetVHost + const VCString& vsVHosts = CZNC::Get().GetVHosts(); + bool bFoundVHost = false; + if (WebSock.IsAdmin() || !WebSock.GetSessionUser()->DenySetVHost()) { + for (unsigned int b = 0; b < vsVHosts.size(); b++) { + const CString& sVHost = vsVHosts[b]; + CTemplate& l = Tmpl.AddRow("VHostLoop"); + + l["VHost"] = sVHost; + + if (pUser && pUser->GetVHost() == sVHost) { + l["Checked"] = "true"; + bFoundVHost = true; + } + } + + // If our current vhost is not in the global list... + if (pUser && !bFoundVHost && !pUser->GetVHost().empty()) { + CTemplate& l = Tmpl.AddRow("VHostLoop"); + + l["VHost"] = pUser->GetVHost(); + l["Checked"] = "true"; + } + } + + vector vDirs; + WebSock.GetAvailSkins(vDirs); + + for (unsigned int d = 0; d < vDirs.size(); d++) { + const CFile& SubDir = vDirs[d]; + CTemplate& l = Tmpl.AddRow("SkinLoop"); + l["Name"] = SubDir.GetShortName(); + + if (SubDir.GetShortName() == (WebSock.IsLoggedIn() ? WebSock.GetSessionUser()->GetSkinName() : CZNC::Get().GetSkinName())) { + l["Checked"] = "true"; + } + } + + set ssUserMods; + CZNC::Get().GetModules().GetAvailableMods(ssUserMods); + + for (set::iterator it = ssUserMods.begin(); it != ssUserMods.end(); ++it) { + const CModInfo& Info = *it; + CTemplate& l = Tmpl.AddRow("ModuleLoop"); + + l["Name"] = Info.GetName(); + l["Description"] = Info.GetDescription(); + l["Args"] = GetModArgs(WebSock, Info.GetName()); + + if (pUser && pUser->GetModules().FindModule(Info.GetName())) { + l["Checked"] = "true"; + } + + if (!WebSock.IsAdmin() && pUser && pUser->DenyLoadMod()) { + l["Disabled"] = "true"; + } + } + + CTemplate& o1 = Tmpl.AddRow("OptionLoop"); + o1["Name"] = "keepbuffer"; + o1["DisplayName"] = "Keep Buffer"; + if (!pUser || pUser->KeepBuffer()) { o1["Checked"] = "true"; } + + /* o2 used to be auto cycle which was removed */ + + CTemplate& o4 = Tmpl.AddRow("OptionLoop"); + o4["Name"] = "multiclients"; + o4["DisplayName"] = "Multi Clients"; + if (!pUser || pUser->MultiClients()) { o4["Checked"] = "true"; } + + CTemplate& o5 = Tmpl.AddRow("OptionLoop"); + o5["Name"] = "bouncedccs"; + o5["DisplayName"] = "Bounce DCCs"; + if (!pUser || pUser->BounceDCCs()) { o5["Checked"] = "true"; } + + CTemplate& o6 = Tmpl.AddRow("OptionLoop"); + o6["Name"] = "useclientip"; + o6["DisplayName"] = "Use Client IP"; + if (pUser && pUser->UseClientIP()) { o6["Checked"] = "true"; } + + CTemplate& o7 = Tmpl.AddRow("OptionLoop"); + o7["Name"] = "appendtimestamp"; + o7["DisplayName"] = "Append Timestamps"; + if (pUser && pUser->GetTimestampAppend()) { o7["Checked"] = "true"; } + + CTemplate& o8 = Tmpl.AddRow("OptionLoop"); + o8["Name"] = "prependtimestamp"; + o8["DisplayName"] = "Prepend Timestamps"; + if (pUser && pUser->GetTimestampPrepend()) { o8["Checked"] = "true"; } + + if (WebSock.IsAdmin()) { + CTemplate& o9 = Tmpl.AddRow("OptionLoop"); + o9["Name"] = "denyloadmod"; + o9["DisplayName"] = "Deny LoadMod"; + if (pUser && pUser->DenyLoadMod()) { o9["Checked"] = "true"; } + + CTemplate& o10 = Tmpl.AddRow("OptionLoop"); + o10["Name"] = "isadmin"; + o10["DisplayName"] = "Admin"; + if (pUser && pUser->IsAdmin()) { o10["Checked"] = "true"; } + if (pUser && pUser == CZNC::Get().FindUser(WebSock.GetUser())) { o10["Disabled"] = "true"; } + + CTemplate& o11 = Tmpl.AddRow("OptionLoop"); + o11["Name"] = "denysetvhost"; + o11["DisplayName"] = "Deny SetVHost"; + if (pUser && pUser->DenySetVHost()) { o11["Checked"] = "true"; } + } + + return true; + } + + /* If pUser is NULL, we are adding a user, else we are editing this one */ + + CString sUsername = WebSock.GetParam("user"); + if (!pUser && CZNC::Get().FindUser(sUsername)) { + WebSock.PrintErrorPage("Invalid Submission [User " + sUsername + " already exists]"); + return true; + } + + CUser* pNewUser = GetNewUser(WebSock, pUser); + if (!pNewUser) { + return true; + } + + CString sErr; + + if (!pUser) { + // Add User Submission + if (!CZNC::Get().AddUser(pNewUser, sErr)) { + delete pNewUser; + WebSock.PrintErrorPage("Invalid submission [" + sErr + "]"); + return true; + } + + if (!CZNC::Get().WriteConfig()) { + WebSock.PrintErrorPage("User added, but config was not written"); + return true; + } + } else { + // Edit User Submission + if (!pUser->Clone(*pNewUser, sErr, false)) { + delete pNewUser; + WebSock.PrintErrorPage("Invalid Submission [" + sErr + "]"); + return true; + } + + delete pNewUser; + if (!CZNC::Get().WriteConfig()) { + WebSock.PrintErrorPage("User edited, but config was not written"); + return true; + } + } + + if (!WebSock.IsAdmin()) { + WebSock.Redirect("edituser"); + } else { + WebSock.Redirect("listusers"); + } + + return true; + } + + bool ListUsersPage(CWebSock& WebSock, CTemplate& Tmpl) { + const map& msUsers = CZNC::Get().GetUserMap(); + Tmpl["Title"] = "List Users"; + Tmpl["Action"] = "listusers"; + + unsigned int a = 0; + + for (map::const_iterator it = msUsers.begin(); it != msUsers.end(); ++it, a++) { + CServer* pServer = it->second->GetCurrentServer(); + CTemplate& l = Tmpl.AddRow("UserLoop"); + CUser& User = *it->second; + + l["Username"] = User.GetUserName(); + l["Clients"] = CString(User.GetClients().size()); + l["IRCNick"] = User.GetIRCNick().GetNick(); + + if (&User == WebSock.GetSessionUser()) { + l["IsSelf"] = "true"; + } + + if (pServer) { + l["Server"] = pServer->GetName(); + } + } + + return true; + } + + bool SettingsPage(CWebSock& WebSock, CTemplate& Tmpl) { + if (!WebSock.GetParam("submitted").ToUInt()) { + CString sVHosts, sMotd; + Tmpl["Action"] = "settings"; + Tmpl["Title"] = "Settings"; + Tmpl["StatusPrefix"] = CZNC::Get().GetStatusPrefix(); + Tmpl["ISpoofFile"] = CZNC::Get().GetISpoofFile(); + Tmpl["ISpoofFormat"] = CZNC::Get().GetISpoofFormat(); + + const VCString& vsVHosts = CZNC::Get().GetVHosts(); + for (unsigned int a = 0; a < vsVHosts.size(); a++) { + CTemplate& l = Tmpl.AddRow("VHostLoop"); + l["VHost"] = vsVHosts[a]; + } + + const VCString& vsMotd = CZNC::Get().GetMotd(); + for (unsigned int b = 0; b < vsMotd.size(); b++) { + CTemplate& l = Tmpl.AddRow("MOTDLoop"); + l["Line"] = vsMotd[b]; + } + + const vector& vpListeners = CZNC::Get().GetListeners(); + for (unsigned int c = 0; c < vpListeners.size(); c++) { + CListener* pListener = vpListeners[c]; + CTemplate& l = Tmpl.AddRow("ListenLoop"); + + l["Port"] = CString(pListener->GetPort()); + l["BindHost"] = pListener->GetBindHost(); + +#ifdef HAVE_LIBSSL + if (pListener->IsSSL()) { + l["IsSSL"] = "true"; + } +#endif + +#ifdef HAVE_IPV6 + if (pListener->IsIPV6()) { + l["IsIPV6"] = "true"; + } +#endif + } + + vector vDirs; + WebSock.GetAvailSkins(vDirs); + + for (unsigned int d = 0; d < vDirs.size(); d++) { + const CFile& SubDir = vDirs[d]; + CTemplate& l = Tmpl.AddRow("SkinLoop"); + l["Name"] = SubDir.GetShortName(); + + if (SubDir.GetShortName() == CZNC::Get().GetSkinName()) { + l["Checked"] = "true"; + } + } + + set ssGlobalMods; + CZNC::Get().GetModules().GetAvailableMods(ssGlobalMods, true); + + for (set::iterator it = ssGlobalMods.begin(); it != ssGlobalMods.end(); ++it) { + const CModInfo& Info = *it; + CTemplate& l = Tmpl.AddRow("ModuleLoop"); + + if (CZNC::Get().GetModules().FindModule(Info.GetName())) { + l["Checked"] = "true"; + } + + if (Info.GetName() == GetModName()) { + l["Disabled"] = "true"; + } + + l["Name"] = Info.GetName(); + l["Description"] = Info.GetDescription(); + l["Args"] = GetModArgs(WebSock, Info.GetName(), true); + } + + return true; + } + + CString sArg; + sArg = WebSock.GetParam("statusprefix"); CZNC::Get().SetStatusPrefix(sArg); + sArg = WebSock.GetParam("ispooffile"); CZNC::Get().SetISpoofFile(sArg); + sArg = WebSock.GetParam("ispoofformat"); CZNC::Get().SetISpoofFormat(sArg); + //sArg = GetParam(""); if (!sArg.empty()) { CZNC::Get().Set(sArg); } + + VCString vsArgs; + WebSock.GetRawParam("motd").Split("\n", vsArgs); + CZNC::Get().ClearMotd(); + + unsigned int a = 0; + for (a = 0; a < vsArgs.size(); a++) { + CZNC::Get().AddMotd(vsArgs[a].TrimRight_n()); + } + + WebSock.GetRawParam("vhosts").Split("\n", vsArgs); + CZNC::Get().ClearVHosts(); + + for (a = 0; a < vsArgs.size(); a++) { + CZNC::Get().AddVHost(vsArgs[a].Trim_n()); + } + + CZNC::Get().SetSkinName(WebSock.GetParam("skin")); + + set ssArgs; + WebSock.GetParamValues("loadmod", ssArgs); + + for (set::iterator it = ssArgs.begin(); it != ssArgs.end(); ++it) { CString sModRet; + CString sModName = (*it).TrimRight_n("\r"); - try { - if (!pNewUser->GetModules().LoadModule(sModName, sArgs, pNewUser, sModRet, (pUser != NULL))) { - DEBUG("Unable to load module [" << sModName << "] [" << sModRet << "]"); + 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, NULL, sModRet)) { + DEBUG("Unable to load module [" << sModName << "] [" << sModRet << "]"); + } + } else if (pMod->GetArgs() != sArgs) { + if (!CZNC::Get().GetModules().ReloadModule(sModName, sArgs, NULL, sModRet)) { + DEBUG("Unable to reload module [" << sModName << "] [" << sModRet << "]"); + } + } else { + DEBUG("Unable to load module [" << sModName << "] because it is already loaded"); } - } catch (...) { - DEBUG("Unable to load module [" << sModName << "]"); } } + + const CModules& vCurMods = CZNC::Get().GetModules(); + set ssUnloadMods; + + for (a = 0; a < vCurMods.size(); a++) { + CModule* pCurMod = vCurMods[a]; + + if (ssArgs.find(pCurMod->GetModName()) == ssArgs.end() && pCurMod->GetModName() != GetModName()) { + ssUnloadMods.insert(pCurMod->GetModName()); + } + } + + for (set::iterator it2 = ssUnloadMods.begin(); it2 != ssUnloadMods.end(); it2++) { + CZNC::Get().GetModules().UnloadModule(*it2); + } + + if (!CZNC::Get().WriteConfig()) { + //WebSock.SetError("Settings changed, but config was not written"); + } + + WebSock.Redirect("settings"); + return true; } - return pNewUser; -} +private: + map m_suSwitchCounters; +}; -CWebAdminAuth::CWebAdminAuth(CWebAdminSock* pWebAdminSock, const CString& sUsername, - const CString& sPassword) - : CAuthBase(sUsername, sPassword, pWebAdminSock) { - m_pWebAdminSock = pWebAdminSock; -} - -void CWebAdminAuth::AcceptedLogin(CUser& User) { - if (m_pWebAdminSock) { - m_pWebAdminSock->SetSessionUser(&User); - m_pWebAdminSock->SetLoggedIn(true); - m_pWebAdminSock->UnPauseRead(); - } -} - -void CWebAdminAuth::RefusedLogin(const CString& sReason) { - if (m_pWebAdminSock) { - m_pWebAdminSock->SetLoggedIn(false); - m_pWebAdminSock->UnPauseRead(); - } -} - -GLOBALMODULEDEFS(CWebAdminMod, "Dynamic configuration of users/settings through a web browser") +GLOBALMODULEDEFS(CWebAdminMod, "Web based administration module") diff --git a/modules/webchat.cpp b/modules/webchat.cpp new file mode 100644 index 00000000..2dcbdf4d --- /dev/null +++ b/modules/webchat.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2004-2010 See the AUTHORS file for details. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include "Chan.h" +#include "HTTPSock.h" +#include "Server.h" +#include "Template.h" +#include "User.h" +#include "znc.h" +#include "WebModules.h" +#include + +using std::stringstream; + +class CWebChatMod : public CModule { +public: + MODCONSTRUCTOR(CWebChatMod) { + } + + virtual ~CWebChatMod() { + } + + virtual bool OnLoad(const CString& sArgStr, CString& sMessage) { + return true; + } + + virtual bool WebRequiresLogin() { return true; } + virtual bool WebRequiresAdmin() { return false; } + virtual CString GetWebNavTitle() { return "webchat"; } + + virtual VWebSubPages& GetSubPages() { + ClearSubPages(); + + // @todo Note: I don't actually suggest we use "sub pages" for the channel nav bar + // The channel tabs should be in the main window and updated via jscript + // Examples of good sub pages would be like Status, Chat, Settings, etc. + // Under the Chat subpage we'd have the jscript client with its own chan tabs + const vector& vChans = m_pUser->GetChans(); + + for (size_t a = 0; a < vChans.size(); a++) { + CString sName(vChans[a]->GetName()); + VPair vParams; + + vParams.push_back(make_pair("c", sName)); + AddSubPage(new CWebSubPage("chan", sName, vParams)); + } + + return CModule::GetSubPages(); + } + + virtual bool OnWebRequest(CWebSock& WebSock, const CString& sPageName, CTemplate& Tmpl) { + std::cerr << "=============================== webchat sPageName=[" << sPageName << "]" << std::endl; + + if (sPageName.empty() || sPageName == "index") { + return true; + } else if (sPageName == "chan") { + return ChannelPage(WebSock, Tmpl); + } + + return false; + } + + bool ChannelPage(CWebSock& WebSock, CTemplate& Tmpl) { + CChan* pChan = m_pUser->FindChan(WebSock.GetParam("c")); + + if (pChan) { + Tmpl["Title"] = pChan->GetName(); + + const VCString& vLines = pChan->GetBuffer(); + + for (size_t a = 0; a < vLines.size(); a++) { + const CString& sLine(vLines[a]); + CNick Nick(sLine.Token(0).LeftChomp_n()); + CTemplate& Row = Tmpl.AddRow("BufferLoop"); + + if (sLine.Token(1).Equals("PRIVMSG")) { + Row["Type"] = "PRIVMSG"; + Row["Nick"] = Nick.GetNick(); + Row["Message"] = sLine.Token(3, true).TrimLeft_n(":"); + } + } + + const map& msNicks = pChan->GetNicks(); + + for (map::const_iterator it = msNicks.begin(); it != msNicks.end(); it++) { + CTemplate& Row = Tmpl.AddRow("NickLoop"); + CNick& Nick = *it->second; + + Row["Nick"] = Nick.GetNick(); + Row["Ident"] = Nick.GetIdent(); + Row["Host"] = Nick.GetHost(); + Row["ModePrefix"] = CString(Nick.GetPermChar()); + } + + return true; + } + + return false; + } + +private: + map m_suSwitchCounters; +}; + +MODULEDEFS(CWebChatMod, "Web based chat") diff --git a/modules/www/notes/files/trash.gif b/modules/www/notes/files/trash.gif new file mode 100644 index 00000000..16a664f7 Binary files /dev/null and b/modules/www/notes/files/trash.gif differ diff --git a/modules/www/notes/index.tmpl b/modules/www/notes/index.tmpl new file mode 100644 index 00000000..d434f5c9 --- /dev/null +++ b/modules/www/notes/index.tmpl @@ -0,0 +1,42 @@ + + +
+ + + + + + + + + + + +
Key:Note: 
+
+ + +You have no notes to display + + + + + + + + + + + + + + + + + + + +
 KeyNote
[del]   
+ + + diff --git a/modules/www/webadmin/add_edit_chan.tmpl b/modules/www/webadmin/add_edit_chan.tmpl new file mode 100644 index 00000000..a46c1722 --- /dev/null +++ b/modules/www/webadmin/add_edit_chan.tmpl @@ -0,0 +1,53 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Channel Info
Channel Name: + + + + +
+ +
Buffer Count:
Default Modes:
Save: checked="checked" />
Options: + + checked="checked" disabled="disabled" />
+ +
+ + +
+ + diff --git a/modules/www/webadmin/add_edit_user.tmpl b/modules/www/webadmin/add_edit_user.tmpl new file mode 100644 index 00000000..9df51ae9 --- /dev/null +++ b/modules/www/webadmin/add_edit_user.tmpl @@ -0,0 +1,258 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Authentication
+ Username: + + + + + + + +
Password:
Confirm password:
Allowed IPs: +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IRC Information
Nickname:
Alt. Nickname:
Status Prefix:
Ident:
Realname:
VHost: +
Quit-MSG:
Servers: +
+ + + + + + + + + +
Module(s)
+ + + + + + + + + + + + + + + + + + +
NameArgumentsDescription
+ checked="checked" disabled="disabled" /> + + +
+
+ + + + + + + + + + + + + + + + + + +
Channel(s)
Default Modes:
+ +
- There are no channels defined -
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
[Add]SaveNameCurModesDefModesBufferCountOptions- Add a channel (opens in same page)
+ + [Edit] [Del] + checked="checked" />
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ZNC Behavior
Skin: + 1 ?> + + + No other skins found + +
Playback Buffer Size:
Timestamp Format:
Timezone offset:
Join Tries:
Max Joins:
Options: + + checked="checked" disabled="disabled" />
+ +
CTCP Replies: +
+ + +
+ + diff --git a/modules/www/webadmin/index.tmpl b/modules/www/webadmin/index.tmpl new file mode 100644 index 00000000..7ffb7416 --- /dev/null +++ b/modules/www/webadmin/index.tmpl @@ -0,0 +1,9 @@ + + +Settings +
+List Users +
+Add User + + diff --git a/modules/www/webadmin/listusers.tmpl b/modules/www/webadmin/listusers.tmpl new file mode 100644 index 00000000..8fece188 --- /dev/null +++ b/modules/www/webadmin/listusers.tmpl @@ -0,0 +1,35 @@ + + + + There are no users defined.
+ Click here, if you would like to add one. + + + + + + + + + + + + + + + + + + + + + +
ActionUsernameClientsCurrent ServerIRC Nick
+ + [Edit] + [Delete] + +
+ + + diff --git a/modules/www/webadmin/settings.tmpl b/modules/www/webadmin/settings.tmpl new file mode 100644 index 00000000..a026e754 --- /dev/null +++ b/modules/www/webadmin/settings.tmpl @@ -0,0 +1,149 @@ + + +
+ + + + + + + + + +
Listen Port(s)
+ + + + + + + + + + + + + + + + + + + + +
PortBindHostSSLIPv6
TrueFalseTrueFalse
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Settings
+ Skin: + + 1 ?> + + + No other skins found + +
+ Status prefix: + + +
+ ISpoofFile: + + +
+ ISpoofFormat: + + +
+ MOTD: + + +
+ VHosts: + + +
+ + + + + + + + + +
Global Module(s)
+ + + + + + + + + + + + + + + + + + +
NameArgumentsDescription
checked="checked" disabled="disabled" />
+
+ +

+ +
+ + diff --git a/modules/www/webchat/chan.tmpl b/modules/www/webchat/chan.tmpl new file mode 100644 index 00000000..2b4c7c4a --- /dev/null +++ b/modules/www/webchat/chan.tmpl @@ -0,0 +1,31 @@ + + + + + + + + + diff --git a/modules/www/webchat/index.tmpl b/modules/www/webchat/index.tmpl new file mode 100644 index 00000000..b2fe71e5 --- /dev/null +++ b/modules/www/webchat/index.tmpl @@ -0,0 +1,5 @@ + + +Welcome to the webchat module. It's currently more like a buffer reply module since there isn't any real-time back and forth communication (yet). + + diff --git a/webskins/_default_/pub/clouds-header.jpg b/webskins/_default_/pub/clouds-header.jpg new file mode 100644 index 00000000..f796ad13 Binary files /dev/null and b/webskins/_default_/pub/clouds-header.jpg differ diff --git a/webskins/_default_/pub/favicon.ico b/webskins/_default_/pub/favicon.ico new file mode 100644 index 00000000..5e28bcf0 Binary files /dev/null and b/webskins/_default_/pub/favicon.ico differ diff --git a/webskins/_default_/pub/main.css b/webskins/_default_/pub/main.css new file mode 100644 index 00000000..05113aab --- /dev/null +++ b/webskins/_default_/pub/main.css @@ -0,0 +1,221 @@ +html, +body { + background: #555; + padding: 0; + margin: 10px 0; + background: #444; + font-family: verdana; + font-size: 12px; + color: white; +} + +img { + border: 0; + padding: 0; + margin: 0; +} + +/* TABLES */ + +table { + border-collapse: collapse; + font-size: 12px; +} + +table.section, +table.data { + width: 100%; + margin-bottom: 15px; + border: 1px solid #f00; +} + +table.section td, +table.data td { + height: 20px; + border: 1px solid #000; + padding: 2px 3px; +} + +table.section thead td, +table.data thead td { + background-color: #D49712; + color: #000; + font-weight: bold; +} + +table.data thead td { + background-color: #EC8E00; +} + +table.data tbody .altrow td { + background-color: #777; +} + +table.section table.data { + width: 95%; + margin: 10 auto; +} + +/* !TABLES */ + +/* FORMS */ + +input, select, textarea { + font-family: verdana; + font-size: 12px; + color: #000000; + border: 1px solid #000000; + background-color: #999; +} + +table.section textarea, +table.section select, +table.section input { + width: 100%; +} + +/* !FORMS */ + +.nowrap { + white-space: nowrap; +} + +/* LINKS */ + +a:link, +a:active, +a:visited, +a:hover { + font-family: verdana; + font-size: 12px; + color: #000; + text-decoration: none; +} + +a:hover { + color: #fff; + text-decoration: underline; +} + +/* !LINKS */ + +#wrapper { + width: 800px; + border: 1px solid #000; + margin-left: auto; + margin-right: auto; +} + +#banner { + background-image: url('clouds-header.jpg'); + padding: 0; + border-bottom: 1px solid #000000; + height: 100px; + width: 800px; + text-align: right; + font-weight: bold; + font-size: 13px; + position: relative; +} + +#banner p { + position: absolute; + bottom: 0; + right: 20px; +} + +#infobar { + width: 800px; + height: 20px; + border-bottom: 1px solid #000; + border-right: 1px solid #000; +} + +#infobar span { + float: left; + padding-left: 5px; +} + +#infobar span.switchuser { + text-align: center; + border-left: 1px solid #000000; + height: 100%; + width: 150px; + float: right; +} + +#subpage { + padding: 10px; +} + +#content { + float: right; + width: 640px; + padding: 0; + background-color: #444; +} + +/* MENU */ + +#menu { + float: left; + background-color: #333; + width: 160px; + margin: 0; + margin-bottom: 20px; + left: 0; +} + +#menu .title, +#menu .item, +#menu .subitem { + vertical-align: middle; + text-align: center; + padding: 8px 5px; + background-color: #777; + border-bottom: 1px solid #000; + border-right: 1px solid #000; +} + +#menu .title { + text-align: left; + padding-left: 3px; + background-color: #333; + font-weight: bold; +} + +#menu .item.active { + background-color: #D49712; + font-weight: bold; +} + +#menu .subitem { + text-align: left; + padding: 3px 5px 3px 10px; + background-color: #999; +} + +#menu .subitem.active { + font-weight: bold; +} + +#menu .subitem.active a:hover { + color: #000; + text-decoration: none; +} + +/* !MENU */ + +#footerbar { + clear: both; + background-color: #D49712; + border-top: 1px solid #000000; + height: 20px; + text-align: right; + padding-right: 5px; +} + +#breadcrumb { + padding: 5px 10px; + border-bottom: 1px solid #000; +} diff --git a/webskins/_default_/tmpl/BaseHeader.tmpl b/webskins/_default_/tmpl/BaseHeader.tmpl new file mode 100644 index 00000000..819a635a --- /dev/null +++ b/webskins/_default_/tmpl/BaseHeader.tmpl @@ -0,0 +1,55 @@ + + + + + + ZNC - <? VAR Title DEFAULT="Web Frontend" ?> + + + + + + + + + + + + + +
+ + + + + +
+ Logged in as: (from: ) + + Logout + + Login + +
+ + + +
+ + + + + +
+ + + +
+ + Called from subpage then finished in Footer.tmpl diff --git a/webskins/_default_/tmpl/Error.tmpl b/webskins/_default_/tmpl/Error.tmpl new file mode 100644 index 00000000..aa3bb68b --- /dev/null +++ b/webskins/_default_/tmpl/Error.tmpl @@ -0,0 +1,3 @@ + +

+ diff --git a/webskins/_default_/tmpl/Footer.tmpl b/webskins/_default_/tmpl/Footer.tmpl new file mode 100644 index 00000000..75f4e925 --- /dev/null +++ b/webskins/_default_/tmpl/Footer.tmpl @@ -0,0 +1,16 @@ +
+ +
+ +
+ + + +
+ +
+ +
+ + + diff --git a/webskins/_default_/tmpl/FooterTag.tmpl b/webskins/_default_/tmpl/FooterTag.tmpl new file mode 100644 index 00000000..66218685 --- /dev/null +++ b/webskins/_default_/tmpl/FooterTag.tmpl @@ -0,0 +1 @@ + ZNC Web Skin "dark-clouds" by David Precious diff --git a/webskins/_default_/tmpl/Header.tmpl b/webskins/_default_/tmpl/Header.tmpl new file mode 100644 index 00000000..3bdc7a6a --- /dev/null +++ b/webskins/_default_/tmpl/Header.tmpl @@ -0,0 +1,18 @@ + + + +This is a wrapper file which simply includes BaseHeader.tmpl so that new skins can make +a Header.tmpl similar to... + + + + +...this way a skin can base itself off of the same html as the default skin but still add +custom css/js + +@todo In the future I'd like to support something like or even + just do a current file vs inc'd file comparison to make sure they aren't the same. + This way we can from the "derived" Header.tmpl and not cause + an recursive loop. + + diff --git a/webskins/_default_/tmpl/Menu.tmpl b/webskins/_default_/tmpl/Menu.tmpl new file mode 100644 index 00000000..ec1a92f3 --- /dev/null +++ b/webskins/_default_/tmpl/Menu.tmpl @@ -0,0 +1,22 @@ +
active">Home
+
active">Help
+ + +
Global Modules:
+ +
+ + + + + + + +
User Modules:
+ +
+ + + + + diff --git a/webskins/_default_/tmpl/help.tmpl b/webskins/_default_/tmpl/help.tmpl new file mode 100644 index 00000000..d5603fd2 --- /dev/null +++ b/webskins/_default_/tmpl/help.tmpl @@ -0,0 +1,5 @@ + + +This is the help section. A quick tutorial with links to the wiki should go here before we release. + + diff --git a/webskins/_default_/tmpl/index.tmpl b/webskins/_default_/tmpl/index.tmpl new file mode 100644 index 00000000..058b8744 --- /dev/null +++ b/webskins/_default_/tmpl/index.tmpl @@ -0,0 +1,3 @@ + + Welcome to ZNC's web interface! + diff --git a/webskins/forest/pub/forest-header.png b/webskins/forest/pub/forest-header.png new file mode 100644 index 00000000..c52d0731 Binary files /dev/null and b/webskins/forest/pub/forest-header.png differ diff --git a/webskins/forest/pub/forest.css b/webskins/forest/pub/forest.css new file mode 100644 index 00000000..552d6ae2 --- /dev/null +++ b/webskins/forest/pub/forest.css @@ -0,0 +1,21 @@ +table.section thead td, +table.data thead td { + background-color: #049712; +} + +table.data thead td { + background-color: #007700; +} + +#banner { + background-image: url('forest-header.png'); +} + +#menu .item.active { + background-color: #049712; + font-weight: bold; +} + +#footerbar { + background-color: #049712; +} diff --git a/webskins/forest/tmpl/FooterTag.tmpl b/webskins/forest/tmpl/FooterTag.tmpl new file mode 100644 index 00000000..38cd1c09 --- /dev/null +++ b/webskins/forest/tmpl/FooterTag.tmpl @@ -0,0 +1 @@ + ZNC "forest" Web Skin - based on "dark-clouds" by David Precious diff --git a/webskins/forest/tmpl/Header.tmpl b/webskins/forest/tmpl/Header.tmpl new file mode 100644 index 00000000..94e4975e --- /dev/null +++ b/webskins/forest/tmpl/Header.tmpl @@ -0,0 +1,2 @@ + + diff --git a/znc.cpp b/znc.cpp index b046dafc..fd5b5cbf 100644 --- a/znc.cpp +++ b/znc.cpp @@ -575,6 +575,11 @@ bool CZNC::WriteConfig() { if (!m_sPidFile.empty()) { m_LockFile.Write("PidFile = " + m_sPidFile.FirstLine() + "\n"); } + + if (!m_sSkinName.empty()) { + m_LockFile.Write("Skin = " + m_sSkinName.FirstLine() + "\n"); + } + if (!m_sStatusPrefix.empty()) { m_LockFile.Write("StatusPrefix = " + m_sStatusPrefix.FirstLine() + "\n"); } @@ -1418,6 +1423,9 @@ bool CZNC::DoRehash(CString& sError) } else if (sName.Equals("MaxJoins")) { pUser->SetMaxJoins(sValue.ToUInt()); continue; + } else if (sName.Equals("Skin")) { + pUser->SetSkinName(sValue); + continue; } else if (sName.Equals("LoadModule")) { CString sModName = sValue.Token(0); CUtils::PrintAction("Loading Module [" + sModName + "]"); @@ -1570,6 +1578,9 @@ bool CZNC::DoRehash(CString& sError) } else if (sName.Equals("PidFile")) { m_sPidFile = sValue; continue; + } else if (sName.Equals("Skin")) { + SetSkinName(sValue); + continue; } else if (sName.Equals("StatusPrefix")) { m_sStatusPrefix = sValue; continue; @@ -1751,6 +1762,24 @@ void CZNC::Broadcast(const CString& sMessage, bool bAdminOnly, } } +CModule* CZNC::FindModule(const CString& sModName, const CString& sUsername) { + if (sUsername.empty()) { + return CZNC::Get().GetModules().FindModule(sModName); + } + + CUser* pUser = FindUser(sUsername); + + return (!pUser) ? NULL : pUser->GetModules().FindModule(sModName); +} + +CModule* CZNC::FindModule(const CString& sModName, CUser* pUser) { + if (pUser) { + return pUser->GetModules().FindModule(sModName); + } + + return CZNC::Get().GetModules().FindModule(sModName); +} + CUser* CZNC::FindUser(const CString& sUsername) { map::iterator it = m_msUsers.find(sUsername); diff --git a/znc.h b/znc.h index c5bd934f..ef4f81c7 100644 --- a/znc.h +++ b/znc.h @@ -82,6 +82,7 @@ public: // Setters void SetConfigState(enum ConfigState e) { m_eConfigState = e; } + void SetSkinName(const CString& s) { m_sSkinName = s; } void SetStatusPrefix(const CString& s) { m_sStatusPrefix = (s.empty()) ? "*" : s; } void SetISpoofFile(const CString& s) { m_sISpoofFile = s; } void SetISpoofFormat(const CString& s) { m_sISpoofFormat = (s.empty()) ? "global { reply \"%\" }" : s; } @@ -95,6 +96,7 @@ public: CGlobalModules& GetModules() { return *m_pModules; } size_t FilterUncommonModules(set& ssModules); #endif + CString GetSkinName() const { return m_sSkinName; } const CString& GetStatusPrefix() const { return m_sStatusPrefix; } const CString& GetCurPath() const { if (!CFile::Exists(m_sCurPath)) { CDir::MakeDir(m_sCurPath); } return m_sCurPath; } const CString& GetHomePath() const { if (!CFile::Exists(m_sHomePath)) { CDir::MakeDir(m_sHomePath); } return m_sHomePath; } @@ -115,6 +117,8 @@ public: // Static allocator static CZNC& Get(); CUser* FindUser(const CString& sUsername); + CModule* FindModule(const CString& sModName, const CString& sUsername); + CModule* FindModule(const CString& sModName, CUser* pUser); bool DeleteUser(const CString& sUsername); bool AddUser(CUser* pUser, CString& sErrorRet); const map & GetUserMap() const { return(m_msUsers); } @@ -154,6 +158,7 @@ protected: CString m_sZNCPath; CString m_sConfigFile; + CString m_sSkinName; CString m_sStatusPrefix; CString m_sISpoofFile; CString m_sOrigISpoof; @@ -263,7 +268,7 @@ protected: bool m_bIPV6; unsigned short m_uPort; CString m_sBindHost; - CRealListener* m_pListener; + CRealListener* m_pListener; }; #endif // !_ZNC_H