]> jfr.im git - irc/rizon/znc.git/blame - WebModules.cpp
Write forceserver and webircpassword to conf
[irc/rizon/znc.git] / WebModules.cpp
CommitLineData
8e596098 1/*
b9b0fd4c 2 * Copyright (C) 2004-2011 See the AUTHORS file for details.
8e596098 3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 2 as published
6 * by the Free Software Foundation.
7 */
8
ad92c58c 9#include "WebModules.h"
3f24f287 10#include "FileUtils.h"
ad92c58c 11#include "User.h"
12#include "znc.h"
13#include <sstream>
14
38d61299 15/// @todo Do we want to make this a configure option?
16#define _SKINDIR_ _DATADIR_ "/webskins"
17
bf6dc459 18const unsigned int CWebSock::m_uiMaxSessions = 5;
19
20// We need this class to make sure the contained maps and their content is
21// destroyed in the order that we want.
22struct CSessionManager {
23 // Sessions are valid for a day, (24h, ...)
24 CSessionManager() : m_mspSessions(24 * 60 * 60 * 1000) {}
25 ~CSessionManager() {
26 // Make sure all sessions are destroyed before any of our maps
27 // are destroyed
28 m_mspSessions.Clear();
29 }
30
31 CWebSessionMap m_mspSessions;
32 std::multimap<CString, CWebSession*> m_mIPSessions;
33};
577a097e 34typedef std::multimap<CString, CWebSession*>::iterator mIPSessionsIterator;
35
bf6dc459 36static CSessionManager Sessions;
37
ad442f4f
US
38class CWebAuth : public CAuthBase {
39public:
40 CWebAuth(CWebSock* pWebSock, const CString& sUsername, const CString& sPassword);
41 virtual ~CWebAuth() {}
42
43 void SetWebSock(CWebSock* pWebSock) { m_pWebSock = pWebSock; }
44 void AcceptedLogin(CUser& User);
45 void RefusedLogin(const CString& sReason);
46 void Invalidate();
47private:
48protected:
49 CWebSock* m_pWebSock;
50};
51
bf6dc459 52void CWebSock::FinishUserSessions(const CUser& User) {
53 Sessions.m_mspSessions.FinishUserSessions(User);
54}
913a3c8d 55
577a097e 56CWebSession::~CWebSession() {
57 // Find our entry in mIPSessions
58 pair<mIPSessionsIterator, mIPSessionsIterator> p =
bf6dc459 59 Sessions.m_mIPSessions.equal_range(m_sIP);
577a097e 60 mIPSessionsIterator it = p.first;
61 mIPSessionsIterator end = p.second;
62
63 while (it != end) {
64 if (it->second == this) {
bf6dc459 65 Sessions.m_mIPSessions.erase(it++);
577a097e 66 } else {
67 ++it;
68 }
69 }
70}
c9f26ba0 71
ad92c58c 72CZNCTagHandler::CZNCTagHandler(CWebSock& WebSock) : CTemplateTagHandler(), m_WebSock(WebSock) {
73}
74
75bool CZNCTagHandler::HandleTag(CTemplate& Tmpl, const CString& sName, const CString& sArgs, CString& sOutput) {
76 if (sName.Equals("URLPARAM")) {
77 //sOutput = CZNC::Get()
9c5068a7 78 sOutput = m_WebSock.GetParam(sArgs.Token(0), false);
ad92c58c 79 return true;
80 }
81
82 return false;
83}
84
577a097e 85CWebSession::CWebSession(const CString& sId, const CString& sIP) : m_sId(sId), m_sIP(sIP) {
c27713cc 86 m_pUser = NULL;
bf6dc459 87 Sessions.m_mIPSessions.insert(make_pair(sIP, this));
c27713cc 88}
89
90bool CWebSession::IsAdmin() const { return IsLoggedIn() && m_pUser->IsAdmin(); }
91
ad92c58c 92CWebAuth::CWebAuth(CWebSock* pWebSock, const CString& sUsername, const CString& sPassword)
93 : CAuthBase(sUsername, sPassword, pWebSock) {
94 m_pWebSock = pWebSock;
95}
96
c27713cc 97void CWebSession::ClearMessageLoops() {
98 m_vsErrorMsgs.clear();
99 m_vsSuccessMsgs.clear();
100}
101
102void CWebSession::FillMessageLoops(CTemplate& Tmpl) {
103 for (unsigned int a = 0; a < m_vsErrorMsgs.size(); a++) {
104 CTemplate& Row = Tmpl.AddRow("ErrorLoop");
105 Row["Message"] = m_vsErrorMsgs[a];
106 }
107
108 for (unsigned int b = 0; b < m_vsSuccessMsgs.size(); b++) {
109 CTemplate& Row = Tmpl.AddRow("SuccessLoop");
110 Row["Message"] = m_vsSuccessMsgs[b];
111 }
112}
113
114size_t CWebSession::AddError(const CString& sMessage) {
115 m_vsErrorMsgs.push_back(sMessage);
116 return m_vsErrorMsgs.size();
117}
118
119size_t CWebSession::AddSuccess(const CString& sMessage) {
120 m_vsSuccessMsgs.push_back(sMessage);
121 return m_vsSuccessMsgs.size();
122}
123
c3728f4c 124void CWebSessionMap::FinishUserSessions(const CUser& User) {
125 iterator it = m_mItems.begin();
126
127 while (it != m_mItems.end()) {
128 if (it->second.second->GetUser() == &User) {
129 m_mItems.erase(it++);
130 } else {
131 ++it;
132 }
133 }
134}
c27713cc 135
ad92c58c 136void CWebAuth::AcceptedLogin(CUser& User) {
137 if (m_pWebSock) {
c27713cc 138 CSmartPtr<CWebSession> spSession = m_pWebSock->GetSession();
139
140 spSession->SetUser(&User);
c27713cc 141
ad92c58c 142 m_pWebSock->SetLoggedIn(true);
143 m_pWebSock->UnPauseRead();
bf5e56c0 144 m_pWebSock->Redirect("/?cookie_check=true");
c27713cc 145
146 DEBUG("Successful login attempt ==> USER [" + User.GetUserName() + "] ==> SESSION [" + spSession->GetId() + "]");
ad92c58c 147 }
148}
149
150void CWebAuth::RefusedLogin(const CString& sReason) {
151 if (m_pWebSock) {
c27713cc 152 CSmartPtr<CWebSession> spSession = m_pWebSock->GetSession();
153
154 spSession->AddError("Invalid login!");
155 spSession->SetUser(NULL);
c27713cc 156
ad92c58c 157 m_pWebSock->SetLoggedIn(false);
158 m_pWebSock->UnPauseRead();
bf5e56c0 159 m_pWebSock->Redirect("/?cookie_check=true");
c27713cc 160
161 DEBUG("UNSUCCESSFUL login attempt ==> REASON [" + sReason + "] ==> SESSION [" + spSession->GetId() + "]");
ad92c58c 162 }
163}
164
547b6034 165void CWebAuth::Invalidate() {
166 CAuthBase::Invalidate();
167 m_pWebSock = NULL;
168}
169
f4b4eafc 170CWebSock::CWebSock() : CHTTPSock(NULL) {
ad92c58c 171 m_bPathsSet = false;
172
173 m_Template.AddTagHandler(new CZNCTagHandler(*this));
174}
175
ad92c58c 176CWebSock::~CWebSock() {
177 if (!m_spAuth.IsNull()) {
547b6034 178 m_spAuth->Invalidate();
ad92c58c 179 }
180
f4b4eafc 181 // we have to account for traffic here because CSocket does
182 // not have a valid CModule* pointer.
5ff69d8e 183 CUser *pUser = GetSession()->GetUser();
184 if (pUser) {
185 pUser->AddBytesWritten(GetBytesWritten());
186 pUser->AddBytesRead(GetBytesRead());
187 } else {
188 CZNC::Get().AddBytesWritten(GetBytesWritten());
189 CZNC::Get().AddBytesRead(GetBytesRead());
190 }
191
77940130 192 // bytes have been accounted for, so make sure they don't get again:
193 ResetBytesWritten();
194 ResetBytesRead();
ad92c58c 195}
196
197void CWebSock::ParsePath() {
198 // The URI looks like:
3f624667 199 // /[module][/page][?arg1=val1&arg2=val2...]
ad92c58c 200
201 m_sPath = GetPath().TrimLeft_n("/");
202
203 m_sPath.TrimPrefix("mods/");
204 m_sPath.TrimPrefix("modfiles/");
205
206 m_sModName = m_sPath.Token(0, false, "/");
207 m_sPage = m_sPath.Token(1, true, "/");
208
ad92c58c 209 if (m_sPage.empty()) {
210 m_sPage = "index";
211 }
212
3f624667 213 DEBUG("Path [" + m_sPath + "], Module [" + m_sModName + "], Page [" + m_sPage + "]");
ad92c58c 214}
215
ad442f4f 216void CWebSock::GetAvailSkins(VCString& vRet) const {
ad92c58c 217 vRet.clear();
218
219 CString sRoot(GetSkinPath("_default_"));
220
221 sRoot.TrimRight("/");
222 sRoot.TrimRight("_default_");
223 sRoot.TrimRight("/");
224
225 if (!sRoot.empty()) {
226 sRoot += "/";
227 }
228
229 if (!sRoot.empty() && CFile::IsDir(sRoot)) {
230 CDir Dir(sRoot);
231
232 for (unsigned int d = 0; d < Dir.size(); d++) {
233 const CFile& SubDir = *Dir[d];
234
235 if (SubDir.IsDir() && SubDir.GetShortName() == "_default_") {
ad442f4f
US
236 vRet.push_back(SubDir.GetShortName());
237 break;
ad92c58c 238 }
239 }
240
241 for (unsigned int e = 0; e < Dir.size(); e++) {
242 const CFile& SubDir = *Dir[e];
243
244 if (SubDir.IsDir() && SubDir.GetShortName() != "_default_" && SubDir.GetShortName() != ".svn") {
ad442f4f 245 vRet.push_back(SubDir.GetShortName());
ad92c58c 246 }
247 }
248 }
ad92c58c 249}
250
dffabfed 251VCString CWebSock::GetDirs(CModule* pModule, bool bIsTemplate) {
ad92c58c 252 CString sHomeSkinsDir(CZNC::Get().GetZNCPath() + "/webskins/");
253 CString sSkinName(GetSkinName());
dffabfed 254 VCString vsResult;
ad92c58c 255
ad92c58c 256 // Module specific paths
257
258 if (pModule) {
259 const CString& sModName(pModule->GetModName());
260
261 // 1. ~/.znc/webskins/<user_skin_setting>/mods/<mod_name>/
262 //
263 if (!sSkinName.empty()) {
dffabfed 264 vsResult.push_back(GetSkinPath(sSkinName) + "/mods/" + sModName + "/");
ad92c58c 265 }
266
267 // 2. ~/.znc/webskins/_default_/mods/<mod_name>/
268 //
dffabfed 269 vsResult.push_back(GetSkinPath("_default_") + "/mods/" + sModName + "/");
ad92c58c 270
c6b5a4d4 271 // 3. ./modules/<mod_name>/tmpl/
ad92c58c 272 //
dffabfed 273 vsResult.push_back(pModule->GetModDataDir() + "/tmpl/");
ad92c58c 274
275 // 4. ~/.znc/webskins/<user_skin_setting>/mods/<mod_name>/
276 //
277 if (!sSkinName.empty()) {
dffabfed 278 vsResult.push_back(GetSkinPath(sSkinName) + "/mods/" + sModName + "/");
ad92c58c 279 }
280
281 // 5. ~/.znc/webskins/_default_/mods/<mod_name>/
282 //
dffabfed 283 vsResult.push_back(GetSkinPath("_default_") + "/mods/" + sModName + "/");
ad92c58c 284 }
285
286 // 6. ~/.znc/webskins/<user_skin_setting>/
287 //
288 if (!sSkinName.empty()) {
dffabfed 289 vsResult.push_back(GetSkinPath(sSkinName) + CString(bIsTemplate ? "/tmpl/" : "/"));
ad92c58c 290 }
291
292 // 7. ~/.znc/webskins/_default_/
293 //
dffabfed 294 vsResult.push_back(GetSkinPath("_default_") + CString(bIsTemplate ? "/tmpl/" : "/"));
295
296 return vsResult;
297}
298
299CString CWebSock::FindTmpl(CModule* pModule, const CString& sName) {
300 VCString vsDirs = GetDirs(pModule, true);
41701817 301 CString sFile = pModule->GetModName() + "_" + sName;
dffabfed 302 for (size_t i = 0; i < vsDirs.size(); ++i) {
41701817 303 if (CFile::Exists(CDir::ChangeDir(vsDirs[i], sFile))) {
304 m_Template.AppendPath(vsDirs[i]);
305 return sFile;
dffabfed 306 }
307 }
308 return sName;
309}
310
311void CWebSock::SetPaths(CModule* pModule, bool bIsTemplate) {
312 m_Template.ClearPaths();
313
314 VCString vsDirs = GetDirs(pModule, bIsTemplate);
315 for (size_t i = 0; i < vsDirs.size(); ++i) {
316 m_Template.AppendPath(vsDirs[i]);
317 }
ad92c58c 318
319 m_bPathsSet = true;
320}
321
322void CWebSock::SetVars() {
323 m_Template["SessionUser"] = GetUser();
324 m_Template["SessionIP"] = GetRemoteIP();
5dcea36f 325 m_Template["Tag"] = CZNC::GetTag(GetSession()->GetUser() != NULL);
ad92c58c 326 m_Template["SkinName"] = GetSkinName();
298d7f09 327 m_Template["_CSRF_Check"] = GetCSRFCheck();
ad92c58c 328
4d149f3d 329 if (GetSession()->IsAdmin()) {
ad92c58c 330 m_Template["IsAdmin"] = "true";
331 }
332
4d149f3d 333 GetSession()->FillMessageLoops(m_Template);
334 GetSession()->ClearMessageLoops();
c27713cc 335
ad92c58c 336 // Global Mods
337 CGlobalModules& vgMods = CZNC::Get().GetModules();
338 for (unsigned int a = 0; a < vgMods.size(); a++) {
339 AddModLoop("GlobalModLoop", *vgMods[a]);
340 }
341
342 // User Mods
343 if (IsLoggedIn()) {
4d149f3d 344 CModules& vMods = GetSession()->GetUser()->GetModules();
ad92c58c 345
346 for (unsigned int a = 0; a < vMods.size(); a++) {
347 AddModLoop("UserModLoop", *vMods[a]);
348 }
349 }
350
351 if (IsLoggedIn()) {
352 m_Template["LoggedIn"] = "true";
353 }
354}
355
356bool CWebSock::AddModLoop(const CString& sLoopName, CModule& Module) {
45caa26d 357 CString sTitle(Module.GetWebMenuTitle());
ad92c58c 358
4d149f3d 359 if (!sTitle.empty() && (IsLoggedIn() || (!Module.WebRequiresLogin() && !Module.WebRequiresAdmin())) && (GetSession()->IsAdmin() || !Module.WebRequiresAdmin())) {
ad92c58c 360 CTemplate& Row = m_Template.AddRow(sLoopName);
361
362 Row["ModName"] = Module.GetModName();
363 Row["Title"] = sTitle;
364
365 if (m_sModName == Module.GetModName()) {
366 Row["Active"] = "true";
367 }
368
369 if (Module.GetUser()) {
370 Row["Username"] = Module.GetUser()->GetUserName();
371 }
372
373 VWebSubPages& vSubPages = Module.GetSubPages();
374
375 for (unsigned int a = 0; a < vSubPages.size(); a++) {
ad92c58c 376 TWebSubPage& SubPage = vSubPages[a];
377
aff85c22 378 // bActive is whether or not the current url matches this subpage (params will be checked below)
379 bool bActive = (m_sModName == Module.GetModName() && m_sPage == SubPage->GetName());
380
4d149f3d 381 if (SubPage->RequiresAdmin() && !GetSession()->IsAdmin()) {
99f1efc8 382 continue; // Don't add admin-only subpages to requests from non-admin users
aff85c22 383 }
384
385 CTemplate& SubRow = Row.AddRow("SubPageLoop");
386
ad92c58c 387 SubRow["ModName"] = Module.GetModName();
388 SubRow["PageName"] = SubPage->GetName();
389 SubRow["Title"] = SubPage->GetTitle().empty() ? SubPage->GetName() : SubPage->GetTitle();
390
391 CString& sParams = SubRow["Params"];
392
ad92c58c 393 const VPair& vParams = SubPage->GetParams();
394 for (size_t b = 0; b < vParams.size(); b++) {
395 pair<CString, CString> ssNV = vParams[b];
396
397 if (!sParams.empty()) {
398 sParams += "&";
399 }
400
401 if (!ssNV.first.empty()) {
402 if (!ssNV.second.empty()) {
403 sParams += ssNV.first.Escape_n(CString::EURL);
404 sParams += "=";
405 sParams += ssNV.second.Escape_n(CString::EURL);
406 }
407
9c5068a7 408 if (bActive && GetParam(ssNV.first, false) != ssNV.second) {
ad92c58c 409 bActive = false;
410 }
411 }
412 }
413
414 if (bActive) {
415 SubRow["Active"] = "true";
416 }
417 }
418
419 return true;
420 }
421
422 return false;
423}
424
1d7fdc29 425CWebSock::EPageReqResult CWebSock::PrintStaticFile(const CString& sPath, CString& sPageRet, CModule* pModule) {
ad92c58c 426 SetPaths(pModule);
9b9fd669 427 CString sFile = m_Template.ExpandFile(sPath.TrimLeft_n("/"));
428 DEBUG("About to print [" + sFile+ "]");
429 // Either PrintFile() fails and sends an error page or it suceeds and
430 // sends a result. In both cases we don't have anything more to do.
431 PrintFile(sFile);
432 return PAGE_DONE;
ad92c58c 433}
434
1d7fdc29 435CWebSock::EPageReqResult CWebSock::PrintTemplate(const CString& sPageName, CString& sPageRet, CModule* pModule) {
ad92c58c 436 SetVars();
437 m_Template["PageName"] = sPageName;
438
439 if (pModule) {
440 CUser* pUser = pModule->GetUser();
441 m_Template["ModUser"] = pUser ? pUser->GetUserName() : "";
442 m_Template["ModName"] = pModule->GetModName();
443
444 if (m_Template.find("Title") == m_Template.end()) {
45caa26d 445 m_Template["Title"] = pModule->GetWebMenuTitle();
ad92c58c 446 }
447 }
448
449 if (!m_bPathsSet) {
450 SetPaths(pModule, true);
451 }
452
453 if (m_Template.GetFileName().empty() && !m_Template.SetFile(sPageName + ".tmpl")) {
1d7fdc29 454 return PAGE_NOTFOUND;
ad92c58c 455 }
456
1d7fdc29 457 if (m_Template.PrintString(sPageRet)) {
458 return PAGE_PRINT;
459 } else {
460 return PAGE_NOTFOUND;
461 }
ad92c58c 462}
463
5050b936 464CString CWebSock::GetSkinPath(const CString& sSkinName) {
ad92c58c 465 CString sRet = CZNC::Get().GetZNCPath() + "/webskins/" + sSkinName;
466
467 if (!CFile::IsDir(sRet)) {
468 sRet = CZNC::Get().GetCurPath() + "/webskins/" + sSkinName;
469
470 if (!CFile::IsDir(sRet)) {
471 sRet = CString(_SKINDIR_) + "/" + sSkinName;
472 }
473 }
474
475 return sRet + "/";
476}
477
c27713cc 478bool CWebSock::ForceLogin() {
4d149f3d 479 if (GetSession()->IsLoggedIn()) {
c27713cc 480 return true;
481 }
482
4d149f3d 483 GetSession()->AddError("You must login to view that page");
c27713cc 484 Redirect("/");
485 return false;
486}
487
114bb25a 488CString CWebSock::GetRequestCookie(const CString& sKey) {
489 const CString sPrefixedKey = CString(GetLocalPort()) + "-" + sKey;
039d9507 490 CString sRet;
491
c27713cc 492 if (!m_sModName.empty()) {
114bb25a 493 sRet = CHTTPSock::GetRequestCookie("Mod-" + m_sModName + "-" + sPrefixedKey);
c27713cc 494 }
495
039d9507 496 if (sRet.empty()) {
114bb25a 497 return CHTTPSock::GetRequestCookie(sPrefixedKey);
039d9507 498 }
114bb25a 499
039d9507 500 return sRet;
c27713cc 501}
502
4daa6371 503bool CWebSock::SendCookie(const CString& sKey, const CString& sValue) {
114bb25a 504 const CString sPrefixedKey = CString(GetLocalPort()) + "-" + sKey;
505
c27713cc 506 if (!m_sModName.empty()) {
114bb25a 507 return CHTTPSock::SendCookie("Mod-" + m_sModName + "-" + sPrefixedKey, sValue);
c27713cc 508 }
509
114bb25a 510 return CHTTPSock::SendCookie(sPrefixedKey, sValue);
c27713cc 511}
512
cb193d8f 513void CWebSock::OnPageRequest(const CString& sURI) {
514 CString sPageRet;
1d7fdc29 515 EPageReqResult eRet = OnPageRequestInternal(sURI, sPageRet);
516 switch (eRet) {
517 case PAGE_PRINT:
cb193d8f 518 PrintPage(sPageRet);
1d7fdc29 519 break;
b764aa7b 520 case PAGE_DEFERRED:
5b723fe7 521 // Something else will later call Close()
522 break;
523 case PAGE_DONE:
0c0c5117 524 // Redirect or something like that, it's done, just make sure
525 // the connection will be closed
526 Close(CLT_AFTERWRITE);
b764aa7b 527 break;
377bbf80 528 case PAGE_NOTFOUND:
1d7fdc29 529 default:
cb193d8f 530 PrintNotFound();
1d7fdc29 531 break;
cb193d8f 532 }
533}
534
1d7fdc29 535CWebSock::EPageReqResult CWebSock::OnPageRequestInternal(const CString& sURI, CString& sPageRet) {
4fbca807
US
536 // Check that their session really belongs to their IP address. IP-based
537 // authentication is bad, but here it's just an extra layer that makes
538 // stealing cookies harder to pull off.
539 //
540 // When their IP is wrong, we give them an invalid cookie. This makes
541 // sure that they will get a new cookie on their next request.
a9ba4020 542 if (CZNC::Get().GetProtectWebSessions() && GetSession()->GetIP() != GetRemoteIP()) {
55ba59de
US
543 DEBUG("Expected IP: " << GetSession()->GetIP());
544 DEBUG("Remote IP: " << GetRemoteIP());
4fbca807 545 SendCookie("SessionId", "WRONG_IP_FOR_SESSION");
4556cc7c 546 PrintErrorPage(403, "Access denied", "This session does not belong to your IP.");
547 return PAGE_DONE;
548 }
549
b0d140e2 550 // Check that they really POSTed from one our forms by checking if they
551 // know the "secret" CSRF check value. Don't do this for login since
552 // CSRF against the login form makes no sense and the login form does a
553 // cookies-enabled check which would break otherwise.
554 if (IsPost() && GetParam("_CSRF_Check") != GetCSRFCheck() && sURI != "/login") {
55ba59de
US
555 DEBUG("Expected _CSRF_Check: " << GetCSRFCheck());
556 DEBUG("Actual _CSRF_Check: " << GetParam("_CSRF_Check"));
6d160471 557 PrintErrorPage(403, "Access denied", "POST requests need to send "
b0d140e2 558 "a secret token to prevent cross-site request forgery attacks.");
6d160471 559 return PAGE_DONE;
b0d140e2 560 }
561
4d149f3d 562 SendCookie("SessionId", GetSession()->GetId());
c27713cc 563
4d149f3d 564 if (GetSession()->IsLoggedIn()) {
565 m_sUser = GetSession()->GetUser()->GetUserName();
c27713cc 566 m_bLoggedIn = true;
567 }
ad92c58c 568
569 // Handle the static pages that don't require a login
570 if (sURI == "/") {
9c5068a7 571 if(!m_bLoggedIn && GetParam("cookie_check", false).ToBool() && GetRequestCookie("SessionId").empty()) {
4d149f3d 572 GetSession()->AddError("Your browser does not have cookies enabled for this site!");
bf5e56c0 573 }
ad92c58c 574 return PrintTemplate("index", sPageRet);
575 } else if (sURI == "/favicon.ico") {
576 return PrintStaticFile("/pub/favicon.ico", sPageRet);
cf3fbdff 577 } else if (sURI == "/robots.txt") {
578 return PrintStaticFile("/pub/robots.txt", sPageRet);
ad92c58c 579 } else if (sURI == "/logout") {
ba05f11e 580 GetSession()->SetUser(NULL);
c27713cc 581 SetLoggedIn(false);
582 Redirect("/");
ad92c58c 583
39c5a677 584 // We already sent a reply
5b723fe7 585 return PAGE_DONE;
eaaddf01 586 } else if (sURI == "/login") {
c27713cc 587 if (GetParam("submitted").ToBool()) {
588 m_sUser = GetParam("user");
589 m_sPass = GetParam("pass");
e54b0bda 590 m_bLoggedIn = OnLogin(m_sUser, m_sPass);
c27713cc 591
b764aa7b 592 // AcceptedLogin()/RefusedLogin() will call Redirect()
593 return PAGE_DEFERRED;
ad92c58c 594 }
595
68c4931e 596 Redirect("/"); // the login form is here
597 return PAGE_DONE;
ad92c58c 598 } else if (sURI.Left(5) == "/pub/") {
599 return PrintStaticFile(sURI, sPageRet);
6c5421b0 600 } else if (sURI.Left(11) == "/skinfiles/") {
601 CString sSkinName = sURI.substr(11);
602 CString::size_type uPathStart = sSkinName.find("/");
603 if (uPathStart != CString::npos) {
604 CString sFilePath = sSkinName.substr(uPathStart + 1);
605 sSkinName.erase(uPathStart);
606
607 m_Template.ClearPaths();
608 m_Template.AppendPath(GetSkinPath(sSkinName) + "pub");
609
610 if (PrintFile(m_Template.ExpandFile(sFilePath))) {
611 return PAGE_DONE;
612 } else {
613 return PAGE_NOTFOUND;
614 }
615 }
616 return PAGE_NOTFOUND;
ad92c58c 617 } else if (sURI.Left(6) == "/mods/" || sURI.Left(10) == "/modfiles/") {
618 ParsePath();
619 // Make sure modules are treated as directories
620 if (sURI.Right(1) != "/" && sURI.find(".") == CString::npos && sURI.TrimLeft_n("/mods/").TrimLeft_n("/").find("/") == CString::npos) {
621 Redirect(sURI + "/");
5b723fe7 622 return PAGE_DONE;
ad92c58c 623 }
624
0ca689f2 625 CModule *pModule = CZNC::Get().GetModules().FindModule(m_sModName);
626 if (!pModule) {
627 // Check if GetSession()->GetUser() is NULL and display
628 // an error in that case
629 if (!ForceLogin())
630 return PAGE_DONE;
ad92c58c 631
0ca689f2 632 pModule = GetSession()->GetUser()->GetModules().FindModule(m_sModName);
633 }
3f624667 634
ad92c58c 635 if (!pModule) {
1d7fdc29 636 return PAGE_NOTFOUND;
ad92c58c 637 } else if (pModule->WebRequiresLogin() && !ForceLogin()) {
1d7fdc29 638 return PAGE_PRINT;
4d149f3d 639 } else if (pModule->WebRequiresAdmin() && !GetSession()->IsAdmin()) {
6d160471 640 PrintErrorPage(403, "Forbidden", "You need to be an admin to access this module");
641 return PAGE_DONE;
4d149f3d 642 } else if (!pModule->IsGlobal() && pModule->GetUser() != GetSession()->GetUser()) {
6d160471 643 PrintErrorPage(403, "Forbidden", "You must login as " + pModule->GetUser()->GetUserName() + " in order to view this page");
644 return PAGE_DONE;
437eef7f 645 } else if (pModule->OnWebPreRequest(*this, m_sPage)) {
646 return PAGE_DEFERRED;
ad92c58c 647 }
648
aff85c22 649 VWebSubPages& vSubPages = pModule->GetSubPages();
650
651 for (unsigned int a = 0; a < vSubPages.size(); a++) {
652 TWebSubPage& SubPage = vSubPages[a];
653
654 bool bActive = (m_sModName == pModule->GetModName() && m_sPage == SubPage->GetName());
655
4d149f3d 656 if (bActive && SubPage->RequiresAdmin() && !GetSession()->IsAdmin()) {
6d160471 657 PrintErrorPage(403, "Forbidden", "You need to be an admin to access this page");
658 return PAGE_DONE;
aff85c22 659 }
660 }
661
4d149f3d 662 if (pModule && !pModule->IsGlobal() && (!IsLoggedIn() || pModule->GetUser() != GetSession()->GetUser())) {
ad92c58c 663 AddModLoop("UserModLoop", *pModule);
664 }
665
666 if (sURI.Left(10) == "/modfiles/") {
667 m_Template.AppendPath(GetSkinPath(GetSkinName()) + "/mods/" + m_sModName + "/files/");
c6b5a4d4 668 m_Template.AppendPath(pModule->GetModDataDir() + "/files/");
ad92c58c 669
1d7fdc29 670 if (PrintFile(m_Template.ExpandFile(m_sPage.TrimLeft_n("/")))) {
671 return PAGE_PRINT;
672 } else {
673 return PAGE_NOTFOUND;
674 }
ad92c58c 675 } else {
676 SetPaths(pModule, true);
677
49df8dc2 678 /* if a module returns false from OnWebRequest, it does not
679 want the template to be printed, usually because it did a redirect. */
ad92c58c 680 if (pModule->OnWebRequest(*this, m_sPage, m_Template)) {
0c0c5117 681 // If they already sent a reply, let's assume
682 // they did what they wanted to do.
683 if (SentHeader()) {
684 return PAGE_DONE;
685 }
ad92c58c 686 return PrintTemplate(m_sPage, sPageRet, pModule);
687 }
688
49df8dc2 689 if (!SentHeader()) {
6d160471 690 PrintErrorPage(404, "Not Implemented", "The requested module does not acknowledge web requests");
49df8dc2 691 }
6d160471 692 return PAGE_DONE;
ad92c58c 693 }
694 } else {
695 CString sPage(sURI.Trim_n("/"));
696 if (sPage.length() < 32) {
697 for (unsigned int a = 0; a < sPage.length(); a++) {
698 unsigned char c = sPage[a];
699
700 if ((c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && c != '_') {
1d7fdc29 701 return PAGE_NOTFOUND;
ad92c58c 702 }
703 }
704
705 return PrintTemplate(sPage, sPageRet);
706 }
707 }
708
1d7fdc29 709 return PAGE_NOTFOUND;
ad92c58c 710}
711
712void CWebSock::PrintErrorPage(const CString& sMessage) {
713 m_Template.SetFile("Error.tmpl");
714
715 m_Template["Action"] = "error";
716 m_Template["Title"] = "Error";
717 m_Template["Error"] = sMessage;
718}
719
cbb6e14c 720CSmartPtr<CWebSession> CWebSock::GetSession() {
c27713cc 721 if (!m_spSession.IsNull()) {
722 return m_spSession;
723 }
ad92c58c 724
4daa6371 725 const CString sCookieSessionId = GetRequestCookie("SessionId");
bf6dc459 726 CSmartPtr<CWebSession> *pSession = Sessions.m_mspSessions.GetItem(sCookieSessionId);
ad92c58c 727
f6f7dce1 728 if (pSession != NULL) {
729 // Refresh the timeout
bf6dc459 730 Sessions.m_mspSessions.AddItem((*pSession)->GetId(), *pSession);
0decb1b7 731 m_spSession = *pSession;
4daa6371 732 DEBUG("Found existing session from cookie: [" + sCookieSessionId + "] IsLoggedIn(" + CString((*pSession)->IsLoggedIn() ? "true" : "false") + ")");
f6f7dce1 733 return *pSession;
c27713cc 734 }
735
bf6dc459 736 if (Sessions.m_mIPSessions.count(GetRemoteIP()) > m_uiMaxSessions) {
737 mIPSessionsIterator it = Sessions.m_mIPSessions.find(GetRemoteIP());
55ba59de 738 DEBUG("Remote IP: " << GetRemoteIP() << "; discarding session [" << it->second->GetId() << "]");
2985efb5 739 Sessions.m_mspSessions.RemItem(it->second->GetId());
913a3c8d 740 }
741
cbb6e14c 742 CString sSessionID;
743 do {
744 sSessionID = CString::RandomString(32);
745 sSessionID += ":" + GetRemoteIP() + ":" + CString(GetRemotePort());
746 sSessionID += ":" + GetLocalIP() + ":" + CString(GetLocalPort());
747 sSessionID += ":" + CString(time(NULL));
748 sSessionID = sSessionID.SHA256();
749
750 DEBUG("Auto generated session: [" + sSessionID + "]");
bf6dc459 751 } while (Sessions.m_mspSessions.HasItem(sSessionID));
cbb6e14c 752
577a097e 753 CSmartPtr<CWebSession> spSession(new CWebSession(sSessionID, GetRemoteIP()));
bf6dc459 754 Sessions.m_mspSessions.AddItem(spSession->GetId(), spSession);
ad92c58c 755
12629f8e 756 m_spSession = spSession;
757
c27713cc 758 return spSession;
ad92c58c 759}
760
b0d140e2 761CString CWebSock::GetCSRFCheck() {
762 CSmartPtr<CWebSession> pSession = GetSession();
763 return pSession->GetId().MD5();
764}
765
e54b0bda 766bool CWebSock::OnLogin(const CString& sUser, const CString& sPass) {
ad92c58c 767 DEBUG("=================== CWebSock::OnLogin()");
768 m_spAuth = new CWebAuth(this, sUser, sPass);
769
770 // Some authentication module could need some time, block this socket
771 // until then. CWebAuth will UnPauseRead().
772 PauseRead();
773 CZNC::Get().AuthUser(m_spAuth);
e54b0bda 774
775 // If CWebAuth already set this, don't change it.
776 return IsLoggedIn();
ad92c58c 777}
778
779Csock* CWebSock::GetSockObj(const CString& sHost, unsigned short uPort) {
f31621b3 780 // All listening is done by CListener, thus CWebSock should never have
781 // to listen, but since GetSockObj() is pure virtual...
782 DEBUG("CWebSock::GetSockObj() called - this should never happen!");
783 return NULL;
ad92c58c 784}
785
cbb6e14c 786CString CWebSock::GetSkinName() {
c27713cc 787 CSmartPtr<CWebSession> spSession = GetSession();
788
789 if (spSession->IsLoggedIn() && !spSession->GetUser()->GetSkinName().empty()) {
790 return spSession->GetUser()->GetSkinName();
ad92c58c 791 }
792
793 return CZNC::Get().GetSkinName();
794}
795