#include "main.h"
#include "User.h"
#include "Nick.h"
#include "Modules.h"
#include "Chan.h"
#include "znc.h"
#include "HTTPSock.h"
#include "Server.h"
class CWebAdminMod;
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 Header(const CString& sTitle);
CString Footer();
void PrintMainPage(CString& sPageRet) {
sPageRet = Header("Main Page");
sPageRet += "Welcome to the ZNC webadmin module.\r\n";
sPageRet += Footer();
}
void GetErrorPage(CString& sPageRet, const CString& sError) {
sPageRet = Header("Error");
sPageRet += "
" + sError.Escape_n(CString::EHTML) + " \r\n";
sPageRet += Footer();
}
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);
void ListPage(CString& sPageRet) {
VCString vsParams;
const map& msvsParams = GetParams();
sPageRet = Header("fooooooo");
if (msvsParams.empty()) {
sPageRet += "You passed in no params.\r\n";
} else {
sPageRet += "foo [" + GetParamString().Escape_n(CString::EHTML) + "] ";
for (map::const_iterator it = msvsParams.begin(); it != msvsParams.end(); it++) {
sPageRet += "" + it->first + " \r\n\r\n";
const VCString vsParams = it->second;
for (unsigned int a = 0; a < vsParams.size(); a++) {
sPageRet += "[" + vsParams[a] + "] \r\n";
}
sPageRet += " \r\n";
}
}
sPageRet += Footer();
}
CString GetModArgs(const CString& sModName, bool bGlobal = false) {
if (!bGlobal && !m_pUser) {
return "";
}
CModules& Modules = (bGlobal) ? CZNC::Get().GetModules() : m_pUser->GetModules();
for (unsigned int a = 0; a < Modules.size(); a++) {
CModule* pModule = Modules[a];
if (pModule->GetModName() == sModName) {
return pModule->GetArgs();
}
}
return "";
}
virtual Csock* GetSockObj(const CString& sHost, unsigned short uPort);
bool IsAdmin() const { return m_bAdmin; }
private:
protected:
CWebAdminMod* m_pModule;
CUser* m_pUser;
bool m_bAdmin;
};
class CWebAdminMod : public CGlobalModule {
public:
CWebAdminMod(void *pDLL, CZNC* pZNC, const CString& sModName) : CGlobalModule(pDLL, pZNC, sModName) {
m_uPort = 8080;
}
virtual ~CWebAdminMod() {
for (set::iterator it = m_sSocks.begin(); it != m_sSocks.end(); it++) {
m_pManager->DelSockByAddr(*it);
}
}
virtual bool OnBoot() {
return true;
}
virtual bool OnLoad(const CString& sArgs) {
bool bSSL = false;
CString sPort = sArgs.Token(0);
if (sPort.Left(1) == "+") {
#ifdef HAVE_LIBSSL
sPort.TrimLeft("+");
bSSL = true;
#else
return false;
#endif
}
m_uPort = sPort.ToUInt();
m_sUser = sArgs.Token(1);
m_sPass = sArgs.Token(2);
if (m_sPass.empty()) {
return false;
}
CWebAdminSock* pListenSock = new CWebAdminSock(this);
#ifdef HAVE_LIBSSL
if (bSSL) {
pListenSock->SetPemLocation(m_pZNC->GetPemLocation());
}
#endif
return m_pManager->ListenAll(m_uPort, "WebAdmin::Listener", bSSL, SOMAXCONN, pListenSock);
}
void AddSock(CWebAdminSock* pSock) {
m_sSocks.insert(pSock);
}
void SockDestroyed(CWebAdminSock* pSock) {
m_sSocks.erase(pSock);
}
const CString& GetUser() const { return m_sUser; }
const CString& GetPass() const { return m_sPass; }
private:
unsigned int m_uPort;
CString m_sUser;
CString m_sPass;
set m_sSocks;
};
CString CWebAdminSock::Header(const CString& sTitle) {
CString sRet = "\r\n"
"\r\nZNC - " + sTitle.Escape_n(CString::EHTML) + " \r\n"
"\r\n"
"\r\n"
"" + sTitle.Escape_n(CString::EHTML) + " \r\n"
"\r\n";
if (IsAdmin()) {
sRet += "[Home ] \r\n"
"[Settings ] \r\n"
"[Add User ] \r\n"
"[List Users ] \r\n";
}
sRet += " \r\n";
return sRet;
}
CString CWebAdminSock::Footer() {
return " " + m_pModule->GetZNC()->GetTag() + " \r\n"
"
\r\n\r\n";
}
bool CWebAdminSock::OnLogin(const CString& sUser, const CString& sPass) {
if (GetUser() == m_pModule->GetUser() && GetPass() == m_pModule->GetPass()) {
m_bAdmin = true;
return true;
}
CUser* pUser = m_pModule->GetZNC()->FindUser(GetUser());
if (pUser && pUser->CheckPass(GetPass())) {
m_pUser = pUser;
return true;
}
return false;
}
void CWebAdminSock::ListUsersPage(CString& sPageRet) {
const map& msUsers = m_pModule->GetZNC()->GetUserMap();
sPageRet = Header("List Users");
if (!msUsers.size()) {
sPageRet += "There are no users defined. Click here if you would like to add one.\r\n";
} else {
sPageRet += "\r\n";
}
sPageRet += Footer();
}
Csock* CWebAdminSock::GetSockObj(const CString& sHost, unsigned short uPort) {
CWebAdminSock* pSock = new CWebAdminSock(m_pModule, sHost, uPort);
pSock->SetSockName("WebAdmin::Client");
pSock->SetTimeout(120);
m_pModule->AddSock(pSock);
return pSock;
}
CWebAdminSock::CWebAdminSock(CWebAdminMod* pModule) : CHTTPSock() {
m_pModule = pModule;
m_pUser = NULL;
m_bAdmin = false;
m_pModule->AddSock(this);
}
CWebAdminSock::CWebAdminSock(CWebAdminMod* pModule, const CString& sHostname, unsigned short uPort, int iTimeout) : CHTTPSock(sHostname, uPort, iTimeout) {
m_pModule = pModule;
m_pUser = NULL;
m_bAdmin = false;
m_pModule->AddSock(this);
}
CWebAdminSock::~CWebAdminSock() {
m_pModule->SockDestroyed(this);
}
bool CWebAdminSock::OnPageRequest(const CString& sURI, CString& sPageRet) {
DEBUG_ONLY(cout << "Request for [" << sURI << "] ");
if (!ForceLogin()) {
DEBUG_ONLY(cout << "- User not logged in!" << endl);
return false;
}
if (sURI == "/") {
if (!IsAdmin()) {
Redirect("/edituser");
return false;
}
PrintMainPage(sPageRet);
} else if (sURI == "/settings") {
if (!IsAdmin()) {
return false;
}
if (!SettingsPage(sPageRet)) {
DEBUG_ONLY(cout << "- 302 Redirect" << endl);
return false;
}
} else if (sURI == "/adduser") {
if (!IsAdmin()) {
return false;
}
if (!UserPage(sPageRet)) {
DEBUG_ONLY(cout << "- 302 Redirect" << endl);
return false;
}
} else if (sURI == "/edituser") {
if (!m_pUser) {
m_pUser = m_pModule->GetZNC()->FindUser(GetParam("user"));
}
if (m_pUser) {
if (!UserPage(sPageRet, m_pUser)) {
DEBUG_ONLY(cout << "- 302 Redirect" << endl);
return false;
}
} else {
GetErrorPage(sPageRet, "No such username");
}
} else if (sURI == "/editchan") {
if (!m_pUser) {
m_pUser = m_pModule->GetZNC()->FindUser(GetParam("user"));
}
if (!m_pUser) {
GetErrorPage(sPageRet, "No such username");
return true;
}
CChan* pChan = m_pUser->FindChan(GetParam("chan"));
if (!pChan) {
GetErrorPage(sPageRet, "No such channel");
cerr << "==== [" << GetParam("chan") << "] == [" << (int) pChan << "]" << endl;
return true;
}
if (!ChanPage(sPageRet, pChan)) {
DEBUG_ONLY(cout << "- 302 Redirect" << endl);
return false;
}
} else if (sURI == "/addchan") {
if (!m_pUser) {
m_pUser = m_pModule->GetZNC()->FindUser(GetParam("user"));
}
if (m_pUser) {
if (!ChanPage(sPageRet)) {
DEBUG_ONLY(cout << "- 302 Redirect" << endl);
return false;
}
} else {
GetErrorPage(sPageRet, "No such username");
}
} else if (sURI == "/delchan") {
if (!m_pUser) {
m_pUser = m_pModule->GetZNC()->FindUser(GetParam("user"));
}
if (m_pUser) {
if (!DelChan(sPageRet)) {
DEBUG_ONLY(cout << "- 302 Redirect" << endl);
return false;
}
} else {
GetErrorPage(sPageRet, "No such username");
}
} else if (sURI == "/listusers") {
if (!IsAdmin()) {
return false;
}
ListUsersPage(sPageRet);
} else if (sURI == "/deluser") {
if (!IsAdmin()) {
return false;
}
if (m_pModule->GetZNC()->DeleteUser(GetParam("user"))) {
DEBUG_ONLY(cout << "- 302 Redirect" << endl);
Redirect("/listusers");
return false;
} else {
GetErrorPage(sPageRet, "No such username");
}
//} else if (sURI == "/list") {
// ListPage(sPageRet);
} else {
DEBUG_ONLY(cout << "- 404 Not Found!" << endl);
return false;
}
DEBUG_ONLY(cout << "- 200 OK!" << endl);
return true;
}
bool CWebAdminSock::SettingsPage(CString& sPageRet) {
if (!GetParam("submitted").ToUInt()) {
sPageRet = Header("Settings");
CString sVHosts;
const VCString& vsVHosts = m_pModule->GetZNC()->GetVHosts();
for (unsigned int a = 0; a < vsVHosts.size(); a++) {
sVHosts += vsVHosts[a] + "\r\n";
}
sPageRet += " \r\n";
sPageRet += Footer();
return true;
}
CString sArg;
sArg = GetParam("statusprefix"); m_pModule->GetZNC()->SetStatusPrefix(sArg);
sArg = GetParam("ispooffile"); m_pModule->GetZNC()->SetISpoofFile(sArg);
sArg = GetParam("ispoofformat"); m_pModule->GetZNC()->SetISpoofFormat(sArg);
//sArg = GetParam(""); if (!sArg.empty()) { m_pModule->GetZNC()->Set(sArg); }
VCString vsArgs = GetParam("vhosts").Split("\n");
m_pModule->GetZNC()->ClearVHosts();
unsigned int a = 0;
for (a = 0; a < vsArgs.size(); a++) {
m_pModule->GetZNC()->AddVHost(vsArgs[a].Trim_n());
}
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);
try {
if (!m_pModule->GetZNC()->GetModules().FindModule(sModName)) {
if (!m_pModule->GetZNC()->GetModules().LoadModule(sModName, sArgs, NULL, sModRet)) {
DEBUG_ONLY(cerr << "Unable to load module [" << sModName << "] [" << sModRet << "]" << endl);
}
} else {
DEBUG_ONLY(cerr << "Unable to load module [" << sModName << "] because it is already loaded" << endl);
}
} catch(...) {
DEBUG_ONLY(cerr << "Unable to load module [" << sModName << "] [" << sArgs << "]" << endl);
}
}
}
const CModules& vCurMods = m_pModule->GetZNC()->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++) {
m_pModule->GetZNC()->GetModules().UnloadModule(*it2);
}
if (!m_pModule->GetZNC()->WriteConfig()) {
GetErrorPage(sPageRet, "Settings changed, but config was not written");
return true;
}
Redirect("/");
return false;
}
bool CWebAdminSock::ChanPage(CString& sPageRet, CChan* pChan) {
if (!m_pUser) {
GetErrorPage(sPageRet, "That user doesn't exist");
}
if (!GetParam("submitted").ToUInt()) {
sPageRet = Header(CString((pChan) ? "Edit" : "Add") + " Channel" + CString((pChan) ? (" [" + pChan->GetName() + "]") : "") + " for User [" + m_pUser->GetUserName() + "]");
sPageRet += "\r\n"
" \r\n"
" \r\n";
if (pChan) {
sPageRet += " \r\n";
}
sPageRet += "Channel Info
\r\n"
" \r\n"
" \r\n";
sPageRet += Footer();
return true;
}
CString sChanName = GetParam("name");
if (!pChan) {
if (sChanName.empty()) {
GetErrorPage(sPageRet, "Channel name is a required argument");
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->SetAutoCycle(GetParam("autocycle").ToBool());
pChan->SetKeepBuffer(GetParam("keepbuffer").ToBool());
bool bDetached = GetParam("detached").ToBool();
if (pChan->IsDetached() != bDetached) {
if (bDetached) {
pChan->DetachUser();
} else {
pChan->AttachUser();
}
}
if (!m_pModule->GetZNC()->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("chan");
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 (!m_pModule->GetZNC()->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()) {
sPageRet = Header((pUser) ? CString("Edit User [" + pUser->GetUserName() + "]") : CString("Add User"));
CString sAllowedHosts, sServers, sChans, sCTCPReplies;
if (pUser) {
const set& ssAllowedHosts = pUser->GetAllowedHosts();
for (set::const_iterator it = ssAllowedHosts.begin(); it != ssAllowedHosts.end(); it++) {
sAllowedHosts += *it + "\r\n";
}
const vector& vServers = pUser->GetServers();
for (unsigned int a = 0; a < vServers.size(); a++) {
sServers += vServers[a]->GetString() + "\r\n";
}
const vector& vChans = pUser->GetChans();
for (unsigned int b = 0; b < vChans.size(); b++) {
CChan* pChan = vChans[b];
if (pChan->InConfig()) {
sChans += vChans[b]->GetName() + "\r\n";
}
}
const MCString& msCTCPReplies = pUser->GetCTCPReplies();
for (MCString::const_iterator it2 = msCTCPReplies.begin(); it2 != msCTCPReplies.end(); it2++) {
sCTCPReplies += it2->first + " " + it2->second + "\r\n";
}
}
sPageRet += "\r\n";
sPageRet += " \r\n"
"Authentication
\r\n"
"IRC Information
\r\n"
"Modules
\r\n"
"Channels
\r\n"
"
Default Modes: \r\n"
"
\r\n";
if (pUser) {
CString sURL = "/addchan?user=" + pUser->GetUserName().Escape_n(CString::EURL);
sPageRet += "
";
}
sPageRet += "
\r\n"
"ZNC Behavior
\r\n"
" \r\n"
" \r\n";
sPageRet += Footer();
return true;
}
CString sUsername = GetParam("user");
if (!pUser && m_pModule->GetZNC()->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 (!pNewUser->IsValid(sErr)) {
delete pNewUser;
GetErrorPage(sPageRet, "Invalid submission [" + sErr + "]");
return true;
}
m_pModule->GetZNC()->AddUser(pNewUser);
if (!m_pModule->GetZNC()->WriteConfig()) {
GetErrorPage(sPageRet, "User added, but config was not written");
return true;
}
} else {
// Edit User Submission
if (!pUser->Clone(*pNewUser, sErr)) {
delete pNewUser;
GetErrorPage(sPageRet, "Invalid Submission [" + sErr + "]");
return true;
}
delete pNewUser;
if (!m_pModule->GetZNC()->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;
}
CString sArg = GetParam("password");
if (sArg != GetParam("password2")) {
GetErrorPage(sPageRet, "Invalid Submission [Passwords do not match]");
return NULL;
}
CUser* pNewUser = new CUser(sUsername, m_pModule->GetZNC());
if (!sArg.empty()) {
pNewUser->SetPass(sArg.MD5(), true);
}
VCString vsArgs;
GetParam("servers").Split("\n", vsArgs);
unsigned int a = 0;
for (a = 0; a < vsArgs.size(); a++) {
pNewUser->AddServer(vsArgs[a].Trim_n());
}
GetParam("allowedips").Split("\n", vsArgs);
for (a = 0; a < vsArgs.size(); a++) {
pNewUser->AddAllowedHost(vsArgs[a].Trim_n());
}
GetParam("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());
}
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);
try {
if (!pNewUser->GetModules().LoadModule(sModName, sArgs, pNewUser, sModRet)) {
DEBUG_ONLY(cerr << "Unable to load module [" << sModName << "] [" << sModRet << "]" << endl);
}
} catch (...) {
DEBUG_ONLY(cerr << "Unable to load module [" << sModName << "] [" << sArgs << "]" << endl);
}
}
}
} else if (pUser) {
CModules& Modules = pUser->GetModules();
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)) {
DEBUG_ONLY(cerr << "Unable to load module [" << sModName << "] [" << sModRet << "]" << endl);
}
} catch (...) {
DEBUG_ONLY(cerr << "Unable to load module [" << sModName << "]" << endl);
}
}
}
sArg = GetParam("nick"); if (!sArg.empty()) { pNewUser->SetNick(sArg); }
sArg = GetParam("altnick"); if (!sArg.empty()) { pNewUser->SetAltNick(sArg); }
sArg = GetParam("awaysuffix"); if (!sArg.empty()) { pNewUser->SetAwaySuffix(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("vhost"); if (!sArg.empty()) { pNewUser->SetVHost(sArg); }
sArg = GetParam("quitmsg"); if (!sArg.empty()) { pNewUser->SetQuitMsg(sArg); }
sArg = GetParam("chanmodes"); if (!sArg.empty()) { pNewUser->SetDefaultChanModes(sArg); }
pNewUser->SetBufferCount(GetParam("bufsize").ToUInt());
pNewUser->SetKeepBuffer(GetParam("keepbuffer").ToBool());
pNewUser->SetBounceDCCs(GetParam("bouncedccs").ToBool());
pNewUser->SetAutoCycle(GetParam("autocycle").ToBool());
pNewUser->SetKeepNick(GetParam("keepnick").ToBool());
pNewUser->SetUseClientIP(GetParam("useclientip").ToBool());
if (IsAdmin()) {
pNewUser->SetDenyLoadMod(GetParam("denyloadmod").ToBool());
} else if (pUser) {
pNewUser->SetDenyLoadMod(pUser->DenyLoadMod());
}
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());
}
return pNewUser;
}
GLOBALMODULEDEFS(CWebAdminMod, "Dynamic configuration of users/settings through a web browser")