]>
Commit | Line | Data |
---|---|---|
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 | 18 | const 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. | |
22 | struct 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 | 34 | typedef std::multimap<CString, CWebSession*>::iterator mIPSessionsIterator; |
35 | ||
bf6dc459 | 36 | static CSessionManager Sessions; |
37 | ||
ad442f4f US |
38 | class CWebAuth : public CAuthBase { |
39 | public: | |
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(); | |
47 | private: | |
48 | protected: | |
49 | CWebSock* m_pWebSock; | |
50 | }; | |
51 | ||
bf6dc459 | 52 | void CWebSock::FinishUserSessions(const CUser& User) { |
53 | Sessions.m_mspSessions.FinishUserSessions(User); | |
54 | } | |
913a3c8d | 55 | |
577a097e | 56 | CWebSession::~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 | 72 | CZNCTagHandler::CZNCTagHandler(CWebSock& WebSock) : CTemplateTagHandler(), m_WebSock(WebSock) { |
73 | } | |
74 | ||
75 | bool 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 | 85 | CWebSession::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 | ||
90 | bool CWebSession::IsAdmin() const { return IsLoggedIn() && m_pUser->IsAdmin(); } | |
91 | ||
ad92c58c | 92 | CWebAuth::CWebAuth(CWebSock* pWebSock, const CString& sUsername, const CString& sPassword) |
93 | : CAuthBase(sUsername, sPassword, pWebSock) { | |
94 | m_pWebSock = pWebSock; | |
95 | } | |
96 | ||
c27713cc | 97 | void CWebSession::ClearMessageLoops() { |
98 | m_vsErrorMsgs.clear(); | |
99 | m_vsSuccessMsgs.clear(); | |
100 | } | |
101 | ||
102 | void 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 | ||
114 | size_t CWebSession::AddError(const CString& sMessage) { | |
115 | m_vsErrorMsgs.push_back(sMessage); | |
116 | return m_vsErrorMsgs.size(); | |
117 | } | |
118 | ||
119 | size_t CWebSession::AddSuccess(const CString& sMessage) { | |
120 | m_vsSuccessMsgs.push_back(sMessage); | |
121 | return m_vsSuccessMsgs.size(); | |
122 | } | |
123 | ||
c3728f4c | 124 | void 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 | 136 | void 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 | ||
150 | void 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 | 165 | void CWebAuth::Invalidate() { |
166 | CAuthBase::Invalidate(); | |
167 | m_pWebSock = NULL; | |
168 | } | |
169 | ||
f4b4eafc | 170 | CWebSock::CWebSock() : CHTTPSock(NULL) { |
ad92c58c | 171 | m_bPathsSet = false; |
172 | ||
173 | m_Template.AddTagHandler(new CZNCTagHandler(*this)); | |
174 | } | |
175 | ||
ad92c58c | 176 | CWebSock::~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 | ||
197 | void 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 | 216 | void 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 | 251 | VCString 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 | ||
299 | CString 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 | ||
311 | void 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 | ||
322 | void 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 | ||
356 | bool 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 | 425 | CWebSock::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 | 435 | CWebSock::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 | 464 | CString 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 | 478 | bool 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 | 488 | CString 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 | 503 | bool 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 | 513 | void 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 | 535 | CWebSock::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 | ||
712 | void 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 | 720 | CSmartPtr<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 | 761 | CString CWebSock::GetCSRFCheck() { |
762 | CSmartPtr<CWebSession> pSession = GetSession(); | |
763 | return pSession->GetId().MD5(); | |
764 | } | |
765 | ||
e54b0bda | 766 | bool 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 | ||
779 | Csock* 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 | 786 | CString 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 |