1 //mainpage WEBEM 2 // 3 //Detailed class and method documentation of the WEBEM C++ embedded web server source code. 4 // 5 //Modified, extended etc by Robbert E. Peters/RTSS B.V. 6 #include "stdafx.h" 7 #include "cWebem.h" 8 #include <boost/bind.hpp> 9 #include "reply.hpp" 10 #include "request.hpp" 11 #include "mime_types.hpp" 12 #include "utf.hpp" 13 #include "Base64.h" 14 #include "sha1.hpp" 15 #include "GZipHelper.h" 16 #include <stdarg.h> 17 #include <fstream> 18 #include <sstream> 19 #include <cstdlib> 20 #include "../main/Helper.h" 21 #include "../main/localtime_r.h" 22 #include "../main/Logger.h" 23 24 #define SHORT_SESSION_TIMEOUT 600 // 10 minutes 25 #define LONG_SESSION_TIMEOUT (30 * 86400) // 30 days 26 27 #ifdef _WIN32 28 #define gmtime_r(timep, result) gmtime_s(result, timep) 29 #endif 30 31 #define websocket_protocol "domoticz" 32 33 int m_failcounter = 0; 34 35 namespace http { 36 namespace server { 37 38 /** 39 Webem constructor 40 41 @param[in] server_settings Server settings (IP address, listening port, ssl options...) 42 @param[in] doc_root path to folder containing html e.g. "./" 43 */ cWebem(const server_settings & settings,const std::string & doc_root)44 cWebem::cWebem( 45 const server_settings & settings, 46 const std::string& doc_root) : 47 m_io_service(), 48 m_settings(settings), 49 m_authmethod(AUTH_LOGIN), 50 mySessionStore(NULL), 51 myRequestHandler(doc_root, this), 52 m_DigistRealm("Domoticz.com"), 53 m_session_clean_timer(m_io_service, boost::posix_time::minutes(1)), 54 m_sessions(), // Rene, make sure we initialize m_sessions first, before starting a server 55 myServer(server_factory::create(settings, myRequestHandler)) 56 { 57 // associate handler to timer and schedule the first iteration 58 m_session_clean_timer.async_wait(boost::bind(&cWebem::CleanSessions, this)); 59 m_io_service_thread = std::make_shared<std::thread>(boost::bind(&boost::asio::io_service::run, &m_io_service)); 60 SetThreadName(m_io_service_thread->native_handle(), "Webem_ssncleaner"); 61 } 62 ~cWebem()63 cWebem::~cWebem() 64 { 65 // Remove reference to CWebServer before its deletion (fix a "pure virtual method called" exception on server termination) 66 mySessionStore = NULL; 67 // Delete server (no need with smart pointer) 68 } 69 70 /** 71 72 Start the server. 73 74 This does not return. 75 76 IMPORTANT: This method does not return. If application needs to continue, start new thread with call to this method. 77 78 */ Run()79 void cWebem::Run() 80 { 81 // Start Web server 82 if (myServer != NULL) 83 { 84 myServer->run(); 85 } 86 } 87 88 /** 89 90 Stop and delete the internal server. 91 92 IMPORTANT: To start the server again, delete it and create a new cWebem instance. 93 94 */ Stop()95 void cWebem::Stop() 96 { 97 // Stop session cleaner 98 try 99 { 100 if (!m_io_service.stopped()) 101 { 102 m_io_service.stop(); 103 } 104 if (m_io_service_thread) 105 { 106 m_io_service_thread->join(); 107 m_io_service_thread.reset(); 108 } 109 } 110 catch (...) 111 { 112 _log.Log(LOG_ERROR, "[web:%s] exception thrown while stopping session cleaner", GetPort().c_str()); 113 } 114 // Stop Web server 115 if (myServer != NULL) 116 { 117 myServer->stop(); 118 } 119 } 120 SetAuthenticationMethod(const _eAuthenticationMethod amethod)121 void cWebem::SetAuthenticationMethod(const _eAuthenticationMethod amethod) 122 { 123 m_authmethod = amethod; 124 } 125 SetWebCompressionMode(_eWebCompressionMode gzmode)126 void cWebem::SetWebCompressionMode(_eWebCompressionMode gzmode) 127 { 128 m_gzipmode = gzmode; 129 } 130 131 132 /** 133 134 Create a link between a string ID and a function to calculate the dynamic content of the string 135 136 The function should return a pointer to a character buffer. This should be contain only ASCII characters 137 ( Unicode code points 1 to 127 ) 138 139 @param[in] idname string identifier 140 @param[in] fun pointer to function which calculates the string to be displayed 141 142 */ 143 RegisterIncludeCode(const char * idname,webem_include_function fun)144 void cWebem::RegisterIncludeCode(const char* idname, webem_include_function fun) 145 { 146 myIncludes.insert(std::pair<std::string, webem_include_function >(std::string(idname), fun)); 147 } 148 /** 149 150 Create a link between a string ID and a function to calculate the dynamic content of the string 151 152 The function should return a pointer to wide character buffer. This should contain a wide character UTF-16 encoded unicode string. 153 WEBEM will convert the string to UTF-8 encoding before sending to the browser. 154 155 @param[in] idname string identifier 156 @param[in] fun pointer to function which calculates the string to be displayed 157 158 */ 159 RegisterIncludeCodeW(const char * idname,webem_include_function_w fun)160 void cWebem::RegisterIncludeCodeW(const char* idname, webem_include_function_w fun) 161 { 162 myIncludes_w.insert(std::pair<std::string, webem_include_function_w >(std::string(idname), fun)); 163 } 164 165 RegisterPageCode(const char * pageurl,webem_page_function fun,bool bypassAuthentication)166 void cWebem::RegisterPageCode(const char* pageurl, webem_page_function fun, bool bypassAuthentication) 167 { 168 myPages.insert(std::pair<std::string, webem_page_function >(std::string(pageurl), fun)); 169 if (bypassAuthentication) 170 { 171 RegisterWhitelistURLString(pageurl); 172 } 173 } RegisterPageCodeW(const char * pageurl,webem_page_function fun,bool bypassAuthentication)174 void cWebem::RegisterPageCodeW(const char* pageurl, webem_page_function fun, bool bypassAuthentication) 175 { 176 myPages_w.insert(std::pair<std::string, webem_page_function >(std::string(pageurl), fun)); 177 if (bypassAuthentication) 178 { 179 RegisterWhitelistURLString(pageurl); 180 } 181 } 182 183 184 /** 185 186 Specify link between form and application function to run when form submitted 187 188 @param[in] idname string identifier 189 @param[in] fun fpointer to function 190 191 */ RegisterActionCode(const char * idname,webem_action_function fun)192 void cWebem::RegisterActionCode(const char* idname, webem_action_function fun) 193 { 194 myActions.insert(std::pair<std::string, webem_action_function >(std::string(idname), fun)); 195 } 196 197 //Used by non basic-auth authentication (for example login forms) to bypass returning false when not authenticated RegisterWhitelistURLString(const char * idname)198 void cWebem::RegisterWhitelistURLString(const char* idname) 199 { 200 myWhitelistURLs.push_back(idname); 201 } RegisterWhitelistCommandsString(const char * idname)202 void cWebem::RegisterWhitelistCommandsString(const char* idname) 203 { 204 myWhitelistCommands.push_back(idname); 205 } 206 207 208 /** 209 210 Do not call from application code, used by server to include generated text. 211 212 @param[in/out] reply text to include generated 213 214 The text is searched for "<!--#cWebemX-->". 215 The X can be any string not containing "-->" 216 217 If X has been registered with cWebem then the associated function 218 is called to generate text to be inserted. 219 220 returns true if any text is replaced 221 222 223 */ Include(std::string & reply)224 bool cWebem::Include(std::string& reply) 225 { 226 bool res = false; 227 size_t p = 0; 228 while (1) 229 { 230 // find next request for generated text 231 p = reply.find("<!--#embed", p); 232 if (p == std::string::npos) 233 { 234 break; 235 } 236 size_t q = reply.find("-->", p); 237 if (q == std::string::npos) 238 break; 239 q += 3; 240 241 size_t reply_len = reply.length(); 242 243 // code identifying text generator 244 std::string code = reply.substr(p + 11, q - p - 15); 245 246 // find the function associated with this code 247 std::map < std::string, webem_include_function >::iterator pf = myIncludes.find(code); 248 if (pf != myIncludes.end()) 249 { 250 // insert generated text 251 std::string content_part; 252 try 253 { 254 pf->second(content_part); 255 } 256 catch (...) 257 { 258 259 } 260 reply.insert(p, content_part); 261 res = true; 262 } 263 else 264 { 265 // no function found, look for a wide character fuction 266 std::map < std::string, webem_include_function_w >::iterator pf = myIncludes_w.find(code); 267 if (pf != myIncludes_w.end()) 268 { 269 // function found 270 // get return string and convert from UTF-16 to UTF-8 271 std::wstring content_part_w; 272 try 273 { 274 pf->second(content_part_w); 275 } 276 catch (...) 277 { 278 279 } 280 cUTF utf(content_part_w.c_str()); 281 // insert generated text 282 reply.insert(p, utf.get8()); 283 res = true; 284 } 285 } 286 287 // adjust pointer into text for insertion 288 p = q + reply.length() - reply_len; 289 } 290 return res; 291 } 292 safeGetline(std::istream & is,std::string & line)293 std::istream & safeGetline(std::istream & is, std::string & line) 294 { 295 std::string myline; 296 if (getline(is, myline)) 297 { 298 if (myline.size() && myline[myline.size() - 1] == '\r') 299 { 300 line = myline.substr(0, myline.size() - 1); 301 } 302 else 303 { 304 line = myline; 305 } 306 } 307 return is; 308 } 309 IsAction(const request & req)310 bool cWebem::IsAction(const request& req) 311 { 312 // look for cWebem form action request 313 std::string uri = req.uri; 314 size_t q = uri.find(".webem"); 315 if (q == std::string::npos) 316 return false; 317 return true; 318 } 319 320 /** 321 Do not call from application code, 322 used by server to handle form submissions. 323 324 returns false is authentication is invalid 325 326 */ CheckForAction(WebEmSession & session,request & req)327 bool cWebem::CheckForAction(WebEmSession & session, request& req) 328 { 329 // look for cWebem form action request 330 if (!IsAction(req)) 331 return false; 332 333 req.parameters.clear(); 334 335 std::string uri = ExtractRequestPath(req.uri); 336 337 // find function matching action code 338 size_t q = uri.find(".webem"); 339 std::string code = uri.substr(1, q - 1); 340 std::map < std::string, webem_action_function >::iterator 341 pfun = myActions.find(code); 342 if (pfun == myActions.end()) 343 return false; 344 345 // decode the values 346 347 if (req.method == "POST") 348 { 349 const char *pContent_Type = request::get_req_header(&req, "Content-Type"); 350 if (pContent_Type) 351 { 352 if (strstr(pContent_Type, "multipart/form-data") != NULL) 353 { 354 std::string szContent = req.content; 355 size_t pos; 356 std::string szVariable, szContentType, szValue; 357 358 //first line is our boundary 359 pos = szContent.find("\r\n"); 360 if (pos == std::string::npos) 361 return false; 362 std::string szBoundary = szContent.substr(0, pos); 363 szContent = szContent.substr(pos + 2); 364 365 while (!szContent.empty()) 366 { 367 //Next line will contain our variable name 368 pos = szContent.find("\r\n"); 369 if (pos == std::string::npos) 370 return false; 371 szVariable = szContent.substr(0, pos); 372 szContent = szContent.substr(pos + 2); 373 if (szVariable.find("Content-Disposition") != 0) 374 return true; 375 pos = szVariable.find("name=\""); 376 if (pos == std::string::npos) 377 return false; 378 szVariable = szVariable.substr(pos + 6); 379 pos = szVariable.find("\""); 380 if (pos == std::string::npos) 381 return false; 382 szVariable = szVariable.substr(0, pos); 383 //Next line could be empty, or a Content-Type, if its empty, it is just a string 384 pos = szContent.find("\r\n"); 385 if (pos == std::string::npos) 386 return false; 387 szContentType = szContent.substr(0, pos); 388 szContent = szContent.substr(pos + 2); 389 if ( 390 (szContentType.find("application/octet-stream") != std::string::npos) || 391 (szContentType.find("application/json") != std::string::npos) || 392 (szContentType.find("Content-Type: text/xml") != std::string::npos) || 393 (szContentType.find("Content-Type: text/x-hex") != std::string::npos) || 394 (szContentType.find("Content-Type: image/") != std::string::npos) 395 ) 396 { 397 //Its a file/stream, next line should be empty 398 pos = szContent.find("\r\n"); 399 if (pos == std::string::npos) 400 return false; 401 szContent = szContent.substr(pos + 2); 402 } 403 else 404 { 405 //next line should be empty 406 if (!szContentType.empty()) 407 return false;//dont know this one 408 } 409 pos = szContent.find(szBoundary); 410 if (pos == std::string::npos) 411 return false; 412 szValue = szContent.substr(0, pos - 2); 413 req.parameters.insert(std::pair< std::string, std::string >(szVariable, szValue)); 414 415 szContent = szContent.substr(pos + szBoundary.size()); 416 pos = szContent.find("\r\n"); 417 if (pos == std::string::npos) 418 return false; 419 szContent = szContent.substr(pos + 2); 420 } 421 //we should have at least one value 422 if (req.parameters.empty()) 423 return false; 424 // call the function 425 try 426 { 427 pfun->second(session, req, req.uri); 428 } 429 catch (...) 430 { 431 432 } 433 if ((req.uri[0] == '/') && (m_webRoot.length() > 0)) 434 { 435 // possible incorrect root reference 436 size_t q = req.uri.find(m_webRoot); 437 if (q != 0) 438 { 439 std::string olduri = req.uri; 440 req.uri = m_webRoot + olduri; 441 } 442 } 443 return true; 444 } 445 else if ( 446 (strstr(pContent_Type, "text/plain") != NULL) 447 || (strstr(pContent_Type, "application/json") != NULL) 448 || (strstr(pContent_Type, "application/xml") != NULL) 449 ) 450 { 451 //Raw data 452 req.parameters.insert(std::pair< std::string, std::string >("data", req.content)); 453 // call the function 454 try 455 { 456 pfun->second(session, req, req.uri); 457 } 458 catch (...) 459 { 460 461 } 462 return true; 463 } 464 } 465 uri = req.content; 466 q = 0; 467 } 468 else 469 { 470 q += 7; 471 } 472 473 std::string name; 474 std::string value; 475 476 size_t p = q; 477 int flag_done = 0; 478 while (!flag_done) 479 { 480 q = uri.find("=", p); 481 if (q == std::string::npos) 482 return false; 483 name = uri.substr(p, q - p); 484 p = q + 1; 485 q = uri.find("&", p); 486 if (q != std::string::npos) 487 value = uri.substr(p, q - p); 488 else 489 { 490 value = uri.substr(p); 491 flag_done = 1; 492 } 493 // the browser sends blanks as + 494 while (1) 495 { 496 size_t p = value.find("+"); 497 if (p == std::string::npos) 498 break; 499 value.replace(p, 1, " "); 500 } 501 502 req.parameters.insert(std::pair< std::string, std::string >(name, value)); 503 p = q + 1; 504 } 505 506 // call the function 507 try 508 { 509 pfun->second(session, req, req.uri); 510 } 511 catch (...) 512 { 513 514 } 515 if ((req.uri[0] == '/') && (m_webRoot.length() > 0)) 516 { 517 // possible incorrect root reference 518 size_t q = req.uri.find(m_webRoot); 519 if (q != 0) 520 { 521 std::string olduri = req.uri; 522 req.uri = m_webRoot + olduri; 523 } 524 } 525 526 return true; 527 } 528 IsPageOverride(const request & req,reply & rep)529 bool cWebem::IsPageOverride(const request& req, reply& rep) 530 { 531 std::string request_path; 532 if (!request_handler::url_decode(req.uri, request_path)) 533 { 534 rep = reply::stock_reply(reply::bad_request); 535 return false; 536 } 537 538 request_path = ExtractRequestPath(request_path); 539 540 size_t paramPos = request_path.find_first_of('?'); 541 if (paramPos != std::string::npos) 542 { 543 request_path = request_path.substr(0, paramPos); 544 } 545 546 std::map < std::string, webem_page_function >::iterator 547 pfun = myPages.find(request_path); 548 549 if (pfun != myPages.end()) 550 return true; 551 //check wchar_t 552 std::map < std::string, webem_page_function >::iterator 553 pfunW = myPages_w.find(request_path); 554 if (pfunW != myPages_w.end()) 555 return true; 556 return false; 557 } 558 CheckForPageOverride(WebEmSession & session,request & req,reply & rep)559 bool cWebem::CheckForPageOverride(WebEmSession & session, request& req, reply& rep) 560 { 561 // Decode url to path. 562 std::string request_path; 563 if (!request_handler::url_decode(req.uri, request_path)) 564 { 565 rep = reply::stock_reply(reply::bad_request); 566 return false; 567 } 568 569 request_path = ExtractRequestPath(request_path); 570 571 req.parameters.clear(); 572 573 std::string request_path2 = req.uri; // we need the raw request string to parse the get-request 574 size_t paramPos = request_path2.find_first_of('?'); 575 if (paramPos != std::string::npos) 576 { 577 std::string params = request_path2.substr(paramPos + 1); 578 std::string name; 579 std::string value; 580 581 size_t q = 0; 582 size_t p = q; 583 int flag_done = 0; 584 std::string uri = params; 585 while (!flag_done) 586 { 587 q = uri.find("=", p); 588 if (q == std::string::npos) 589 { 590 break; 591 } 592 name = uri.substr(p, q - p); 593 p = q + 1; 594 q = uri.find("&", p); 595 if (q != std::string::npos) 596 value = uri.substr(p, q - p); 597 else 598 { 599 value = uri.substr(p); 600 flag_done = 1; 601 } 602 // the browser sends blanks as + 603 while (1) 604 { 605 size_t p = value.find("+"); 606 if (p == std::string::npos) 607 break; 608 value.replace(p, 1, " "); 609 } 610 611 // now, url-decode only the value 612 std::string decoded; 613 request_handler::url_decode(value, decoded); 614 req.parameters.insert(std::pair< std::string, std::string >(name, decoded)); 615 p = q + 1; 616 } 617 } 618 if (req.method == "POST") 619 { 620 const char *pContent_Type = request::get_req_header(&req, "Content-Type"); 621 if (pContent_Type) 622 { 623 if (strstr(pContent_Type, "multipart/form-data") != NULL) 624 { 625 std::string szContent = req.content; 626 size_t pos; 627 std::string szVariable, szContentType, szValue; 628 629 //first line is our boundary 630 pos = szContent.find("\r\n"); 631 if (pos == std::string::npos) 632 return true; 633 std::string szBoundary = szContent.substr(0, pos); 634 szContent = szContent.substr(pos + 2); 635 636 while (!szContent.empty()) 637 { 638 //Next line will contain our variable name 639 pos = szContent.find("\r\n"); 640 if (pos == std::string::npos) 641 return true; 642 szVariable = szContent.substr(0, pos); 643 szContent = szContent.substr(pos + 2); 644 if (szVariable.find("Content-Disposition") != 0) 645 return true; 646 pos = szVariable.find("name=\""); 647 if (pos == std::string::npos) 648 return true; 649 szVariable = szVariable.substr(pos + 6); 650 pos = szVariable.find("\""); 651 if (pos == std::string::npos) 652 return true; 653 szVariable = szVariable.substr(0, pos); 654 //Next line could be empty, or a Content-Type, if its empty, it is just a string 655 pos = szContent.find("\r\n"); 656 if (pos == std::string::npos) 657 return true; 658 szContentType = szContent.substr(0, pos); 659 szContent = szContent.substr(pos + 2); 660 if ( 661 (szContentType.find("application/octet-stream") != std::string::npos) 662 || (szContentType.find("application/json") != std::string::npos) 663 || (szContentType.find("application/x-zip") != std::string::npos) 664 || (szContentType.find("Content-Type: text/xml") != std::string::npos) 665 ) 666 { 667 //Its a file/stream, next line should be empty 668 pos = szContent.find("\r\n"); 669 if (pos == std::string::npos) 670 return true; 671 szContent = szContent.substr(pos + 2); 672 } 673 else 674 { 675 //next line should be empty 676 if (!szContentType.empty()) 677 return true;//dont know this one 678 } 679 pos = szContent.find(szBoundary); 680 if (pos == std::string::npos) 681 return true; 682 szValue = szContent.substr(0, pos - 2); 683 req.parameters.insert(std::pair< std::string, std::string >(szVariable, szValue)); 684 685 szContent = szContent.substr(pos + szBoundary.size()); 686 pos = szContent.find("\r\n"); 687 if (pos == std::string::npos) 688 return true; 689 szContent = szContent.substr(pos + 2); 690 } 691 //we should have at least one value 692 if (req.parameters.empty()) 693 return true; 694 } //if (strstr(pContent_Type, "multipart/form-data") != NULL) 695 else if (strstr(pContent_Type, "application/x-www-form-urlencoded") != NULL) 696 { 697 std::string params = req.content; 698 std::string name; 699 std::string value; 700 701 size_t q = 0; 702 size_t p = q; 703 int flag_done = 0; 704 std::string uri = params; 705 while (!flag_done) 706 { 707 q = uri.find("=", p); 708 if (q == std::string::npos) 709 { 710 break; 711 } 712 name = uri.substr(p, q - p); 713 p = q + 1; 714 q = uri.find("&", p); 715 if (q != std::string::npos) 716 value = uri.substr(p, q - p); 717 else 718 { 719 value = uri.substr(p); 720 flag_done = 1; 721 } 722 // the browser sends blanks as + 723 while (1) 724 { 725 size_t p = value.find("+"); 726 if (p == std::string::npos) 727 break; 728 value.replace(p, 1, " "); 729 } 730 731 // now, url-decode only the value 732 std::string decoded; 733 request_handler::url_decode(value, decoded); 734 req.parameters.insert(std::pair< std::string, std::string >(name, decoded)); 735 p = q + 1; 736 } 737 } 738 } 739 } 740 741 // Determine the file extension. 742 std::string extension; 743 if (req.uri.find("json.htm") != std::string::npos) 744 { 745 extension = "json"; 746 } 747 else 748 { 749 std::size_t last_slash_pos = request_path.find_last_of("/"); 750 std::size_t last_dot_pos = request_path.find_last_of("."); 751 if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos) 752 { 753 extension = request_path.substr(last_dot_pos + 1); 754 } 755 } 756 std::string strMimeType = mime_types::extension_to_type(extension); 757 758 std::map < std::string, webem_page_function >::iterator 759 pfun = myPages.find(request_path); 760 761 if (pfun != myPages.end()) 762 { 763 rep.status = reply::ok; 764 try 765 { 766 pfun->second(session, req, rep); 767 } 768 catch (std::exception& e) 769 { 770 _log.Log(LOG_ERROR, "WebServer PO exception occurred : '%s'", e.what()); 771 } 772 catch (...) 773 { 774 _log.Log(LOG_ERROR, "WebServer PO unknown exception occurred"); 775 } 776 std::string attachment; 777 size_t num = rep.headers.size(); 778 for (size_t h = 0; h < num; h++) 779 { 780 if (boost::iequals(rep.headers[h].name, "Content-Disposition")) 781 { 782 attachment = rep.headers[h].value.substr(rep.headers[h].value.find("=") + 1); 783 std::size_t last_dot_pos = attachment.find_last_of("."); 784 if (last_dot_pos != std::string::npos) 785 { 786 extension = attachment.substr(last_dot_pos + 1); 787 strMimeType = mime_types::extension_to_type(extension); 788 } 789 break; 790 } 791 } 792 793 reply::add_header(&rep, "Content-Length", std::to_string(rep.content.size())); 794 if (!boost::algorithm::starts_with(strMimeType, "image")) 795 { 796 if (!strMimeType.empty()) 797 strMimeType += ";charset=UTF-8"; 798 reply::add_header(&rep, "Cache-Control", "no-cache"); 799 reply::add_header(&rep, "Pragma", "no-cache"); 800 reply::add_header(&rep, "Access-Control-Allow-Origin", "*"); 801 } 802 else 803 { 804 reply::add_header(&rep, "Cache-Control", "max-age=3600, public"); 805 } 806 reply::add_header_content_type(&rep, strMimeType); 807 return true; 808 } 809 810 //check wchar_t 811 std::map < std::string, webem_page_function >::iterator 812 pfunW = myPages_w.find(request_path); 813 if (pfunW == myPages_w.end()) 814 return false; 815 816 try 817 { 818 pfunW->second(session, req, rep); 819 } 820 catch (...) 821 { 822 823 } 824 825 rep.status = reply::ok; 826 reply::add_header(&rep, "Content-Length", std::to_string(rep.content.size())); 827 reply::add_header(&rep, "Content-Type", strMimeType + "; charset=UTF-8"); 828 reply::add_header(&rep, "Cache-Control", "no-cache"); 829 reply::add_header(&rep, "Pragma", "no-cache"); 830 reply::add_header(&rep, "Access-Control-Allow-Origin", "*"); 831 832 return true; 833 } 834 SetWebTheme(const std::string & themename)835 void cWebem::SetWebTheme(const std::string &themename) 836 { 837 m_actTheme = "/styles/" + themename; 838 } 839 SetWebRoot(const std::string & webRoot)840 void cWebem::SetWebRoot(const std::string &webRoot) 841 { 842 // remove trailing slash if required 843 if (webRoot.size() > 0 && webRoot[webRoot.size() - 1] == '/') 844 { 845 m_webRoot = webRoot.substr(0, webRoot.size() - 1); 846 } 847 else 848 { 849 m_webRoot = webRoot; 850 } 851 // put slash at the front if required 852 if (m_webRoot.size() > 0 && m_webRoot[0] != '/') 853 { 854 m_webRoot = "/" + webRoot; 855 } 856 } 857 ExtractRequestPath(const std::string & original_request_path)858 std::string cWebem::ExtractRequestPath(const std::string& original_request_path) 859 { 860 std::string request_path(original_request_path); 861 size_t paramPos = request_path.find_first_of('?'); 862 if (paramPos != std::string::npos) 863 { 864 request_path = request_path.substr(0, paramPos); 865 } 866 867 if (request_path.find(m_webRoot + "/@login") == 0) 868 { 869 request_path = m_webRoot + "/"; 870 } 871 872 // If path ends in slash (i.e. is a directory) then add "index.html". 873 if (request_path[request_path.size() - 1] == '/') 874 { 875 request_path += "index.html"; 876 } 877 878 if (m_webRoot.size() > 0) 879 { 880 // remove web root if present otherwise 881 // create invalid request 882 if (request_path.find(m_webRoot) == 0) 883 { 884 request_path = request_path.substr(m_webRoot.size()); 885 } 886 else 887 { 888 request_path = ""; 889 } 890 } 891 892 if (request_path.find("/acttheme/") == 0) 893 { 894 request_path = m_actTheme + request_path.substr(9); 895 } 896 return request_path; 897 } 898 IsBadRequestPath(const std::string & request_path)899 bool cWebem::IsBadRequestPath(const std::string& request_path) 900 { 901 // Request path must be absolute and not contain "..". 902 if (request_path.empty() || request_path[0] != '/' 903 || request_path.find("..") != std::string::npos) 904 { 905 return true; 906 } 907 908 // don't allow access to control files 909 if (request_path.find(".htpasswd") != std::string::npos) 910 { 911 return true; 912 } 913 914 // if we have a web root set the request must start with it 915 if (m_webRoot.size() > 0) 916 { 917 if (request_path.find(m_webRoot) != 0) 918 { 919 return true; 920 } 921 } 922 923 return false; 924 } 925 AddUserPassword(const unsigned long ID,const std::string & username,const std::string & password,const _eUserRights userrights,const int activetabs)926 void cWebem::AddUserPassword(const unsigned long ID, const std::string &username, const std::string &password, const _eUserRights userrights, const int activetabs) 927 { 928 _tWebUserPassword wtmp; 929 wtmp.ID = ID; 930 wtmp.Username = username; 931 wtmp.Password = password; 932 wtmp.userrights = userrights; 933 wtmp.ActiveTabs = activetabs; 934 wtmp.TotSensors = 0; 935 m_userpasswords.push_back(wtmp); 936 } 937 ClearUserPasswords()938 void cWebem::ClearUserPasswords() 939 { 940 m_userpasswords.clear(); 941 942 std::unique_lock<std::mutex> lock(m_sessionsMutex); 943 m_sessions.clear(); //TODO : check if it is really necessary 944 } 945 AddLocalNetworks(std::string network)946 void cWebem::AddLocalNetworks(std::string network) 947 { 948 _tIPNetwork ipnetwork; 949 ipnetwork.network = 0; 950 ipnetwork.mask = 0; 951 ipnetwork.hostname = ""; 952 953 if (network == "") 954 { 955 //add local host 956 char ac[256]; 957 if (gethostname(ac, sizeof(ac)) != SOCKET_ERROR) 958 { 959 ipnetwork.hostname = ac; 960 stdlower(ipnetwork.hostname); 961 m_localnetworks.push_back(ipnetwork); 962 } 963 return; 964 } 965 std::string inetwork = network; 966 std::string inetworkmask = network; 967 std::string mask = network; 968 969 970 size_t pos = network.find_first_of("*"); 971 if (pos > 0) 972 { 973 stdreplace(inetwork, "*", "0"); 974 int a, b, c, d; 975 if (sscanf(inetwork.c_str(), "%d.%d.%d.%d", &a, &b, &c, &d) != 4) 976 return; 977 std::stringstream newnetwork; 978 newnetwork << std::dec << a << "." << std::dec << b << "." << std::dec << c << "." << std::dec << d; 979 inetwork = newnetwork.str(); 980 981 stdreplace(inetworkmask, "*", "999"); 982 int e, f, g, h; 983 if (sscanf(inetworkmask.c_str(), "%d.%d.%d.%d", &e, &f, &g, &h) != 4) 984 return; 985 986 std::stringstream newmask; 987 if (e != 999) newmask << "255"; else newmask << "0"; 988 newmask << "."; 989 if (f != 999) newmask << "255"; else newmask << "0"; 990 newmask << "."; 991 if (g != 999) newmask << "255"; else newmask << "0"; 992 newmask << "."; 993 if (h != 999) newmask << "255"; else newmask << "0"; 994 mask = newmask.str(); 995 996 ipnetwork.network = IPToUInt(inetwork); 997 ipnetwork.mask = IPToUInt(mask); 998 } 999 else 1000 { 1001 pos = network.find_first_of("/"); 1002 if (pos > 0) 1003 { 1004 unsigned char keepbits = (unsigned char)atoi(network.substr(pos + 1).c_str()); 1005 uint32_t imask = keepbits > 0 ? 0x00 - (1 << (32 - keepbits)) : 0xFFFFFFFF; 1006 inetwork = network.substr(0, pos); 1007 ipnetwork.network = IPToUInt(inetwork); 1008 ipnetwork.mask = imask; 1009 } 1010 else 1011 { 1012 //Is it an IP address of hostname? 1013 boost::system::error_code ec; 1014 boost::asio::ip::address::from_string(network, ec); 1015 if (ec) 1016 { 1017 //only allow ip's, localhost is covered above 1018 return; 1019 //ipnetwork.hostname=network; 1020 } 1021 else 1022 { 1023 //single IP? 1024 ipnetwork.network = IPToUInt(inetwork); 1025 ipnetwork.mask = IPToUInt("255.255.255.255"); 1026 } 1027 } 1028 } 1029 1030 m_localnetworks.push_back(ipnetwork); 1031 } 1032 ClearLocalNetworks()1033 void cWebem::ClearLocalNetworks() 1034 { 1035 m_localnetworks.clear(); 1036 } 1037 AddRemoteProxyIPs(const std::string & ipaddr)1038 void cWebem::AddRemoteProxyIPs(const std::string &ipaddr) 1039 { 1040 myRemoteProxyIPs.push_back(ipaddr); 1041 } 1042 ClearRemoteProxyIPs()1043 void cWebem::ClearRemoteProxyIPs() 1044 { 1045 myRemoteProxyIPs.clear(); 1046 } 1047 SetDigistRealm(const std::string & realm)1048 void cWebem::SetDigistRealm(const std::string &realm) 1049 { 1050 m_DigistRealm = realm; 1051 } 1052 SetZipPassword(const std::string & password)1053 void cWebem::SetZipPassword(const std::string &password) 1054 { 1055 m_zippassword = password; 1056 } 1057 SetSessionStore(session_store_impl_ptr sessionStore)1058 void cWebem::SetSessionStore(session_store_impl_ptr sessionStore) 1059 { 1060 mySessionStore = sessionStore; 1061 } 1062 GetSessionStore()1063 session_store_impl_ptr cWebem::GetSessionStore() 1064 { 1065 return mySessionStore; 1066 } 1067 1068 // Check the user's password, return 1 if OK check_password(struct ah * ah,const std::string & ha1,const std::string & realm)1069 static int check_password(struct ah *ah, const std::string &ha1, const std::string &realm) 1070 { 1071 if ( 1072 (ah->nonce.size() == 0) && 1073 (ah->response.size() != 0) 1074 ) 1075 { 1076 return (ha1 == GenerateMD5Hash(ah->response)); 1077 } 1078 return 0; 1079 } 1080 GetPort()1081 const std::string cWebem::GetPort() 1082 { 1083 return m_settings.listening_port; 1084 } 1085 GetWebRoot()1086 const std::string cWebem::GetWebRoot() 1087 { 1088 return m_webRoot; 1089 } 1090 GetSession(const std::string & ssid)1091 WebEmSession * cWebem::GetSession(const std::string & ssid) 1092 { 1093 std::unique_lock<std::mutex> lock(m_sessionsMutex); 1094 std::map<std::string, WebEmSession>::iterator itt = m_sessions.find(ssid); 1095 if (itt != m_sessions.end()) 1096 { 1097 return &itt->second; 1098 } 1099 return NULL; 1100 } 1101 AddSession(const WebEmSession & session)1102 void cWebem::AddSession(const WebEmSession & session) 1103 { 1104 std::unique_lock<std::mutex> lock(m_sessionsMutex); 1105 m_sessions[session.id] = session; 1106 } 1107 RemoveSession(const WebEmSession & session)1108 void cWebem::RemoveSession(const WebEmSession & session) 1109 { 1110 RemoveSession(session.id); 1111 } 1112 RemoveSession(const std::string & ssid)1113 void cWebem::RemoveSession(const std::string & ssid) 1114 { 1115 std::unique_lock<std::mutex> lock(m_sessionsMutex); 1116 std::map<std::string, WebEmSession>::iterator itt = m_sessions.find(ssid); 1117 if (itt != m_sessions.end()) 1118 { 1119 m_sessions.erase(itt); 1120 } 1121 } 1122 CountSessions()1123 int cWebem::CountSessions() 1124 { 1125 std::unique_lock<std::mutex> lock(m_sessionsMutex); 1126 return (int)m_sessions.size(); 1127 } 1128 CleanSessions()1129 void cWebem::CleanSessions() 1130 { 1131 _log.Debug(DEBUG_WEBSERVER, "[web:%s] cleaning sessions...", GetPort().c_str()); 1132 1133 int before = CountSessions(); 1134 // Clean up timed out sessions from memory 1135 std::vector<std::string> ssids; 1136 { 1137 std::unique_lock<std::mutex> lock(m_sessionsMutex); 1138 time_t now = mytime(NULL); 1139 std::map<std::string, WebEmSession>::iterator itt; 1140 for (itt = m_sessions.begin(); itt != m_sessions.end(); ++itt) 1141 { 1142 if (itt->second.timeout < now) 1143 { 1144 ssids.push_back(itt->second.id); 1145 } 1146 } 1147 } 1148 std::vector<std::string>::iterator ssitt; 1149 for (ssitt = ssids.begin(); ssitt != ssids.end(); ++ssitt) 1150 { 1151 std::string ssid = *ssitt; 1152 RemoveSession(ssid); 1153 } 1154 int after = CountSessions(); 1155 std::stringstream ss; 1156 { 1157 std::unique_lock<std::mutex> lock(m_sessionsMutex); 1158 std::map<std::string, WebEmSession>::iterator itt; 1159 int i = 0; 1160 for (itt = m_sessions.begin(); itt != m_sessions.end(); ++itt) 1161 { 1162 if (i > 0) 1163 { 1164 ss << ","; 1165 } 1166 ss << itt->second.id; 1167 i += 1; 1168 } 1169 } 1170 // Clean up expired sessions from database in order to avoid to wait for the domoticz restart (long time running instance) 1171 if (mySessionStore != NULL) 1172 { 1173 this->mySessionStore->CleanSessions(); 1174 } 1175 // Schedule next cleanup 1176 m_session_clean_timer.expires_at(m_session_clean_timer.expires_at() + boost::posix_time::minutes(15)); 1177 m_session_clean_timer.async_wait(boost::bind(&cWebem::CleanSessions, this)); 1178 } 1179 1180 // Return 1 on success. Always initializes the ah structure. parse_auth_header(const request & req,struct ah * ah)1181 int cWebemRequestHandler::parse_auth_header(const request& req, struct ah *ah) 1182 { 1183 const char *auth_header; 1184 1185 if ((auth_header = request::get_req_header(&req, "Authorization")) == NULL) 1186 { 1187 return 0; 1188 } 1189 1190 // X509 Auth header 1191 if (boost::icontains(auth_header, "/CN=")) 1192 { 1193 // DN looks like: /C=Country/ST=State/L=City/O=Org/OU=OrganizationUnit/CN=username/emailAddress=user@mail.com 1194 std::string dn = auth_header; 1195 size_t spos, epos; 1196 1197 spos = dn.find("/CN="); 1198 epos = dn.find("/", spos + 1); 1199 if (spos != std::string::npos) 1200 { 1201 if (epos == std::string::npos) 1202 { 1203 epos = dn.size(); 1204 } 1205 ah->user = dn.substr(spos + 4, epos - spos - 4); 1206 } 1207 1208 spos = dn.find("/emailAddress="); 1209 epos = dn.find("/", spos + 1); 1210 if (spos != std::string::npos) 1211 { 1212 if (epos == std::string::npos) 1213 { 1214 epos = dn.size(); 1215 } 1216 ah->response = dn.substr(spos + 14, epos - spos - 14); 1217 } 1218 1219 if (ah->user.empty()) // TODO: Should ah->response be not empty ? 1220 { 1221 return 0; 1222 } 1223 return 1; 1224 } 1225 // Basic Auth header 1226 else if (boost::icontains(auth_header, "Basic ")) 1227 { 1228 std::string decoded = base64_decode(auth_header + 6); 1229 size_t npos = decoded.find(':'); 1230 if (npos == std::string::npos) 1231 { 1232 return 0; 1233 } 1234 1235 ah->user = decoded.substr(0, npos); 1236 ah->response = decoded.substr(npos + 1); 1237 return 1; 1238 } 1239 1240 return 0; 1241 } 1242 1243 // Authorize against the opened passwords file. Return 1 if authorized. authorize(WebEmSession & session,const request & req,reply & rep)1244 int cWebemRequestHandler::authorize(WebEmSession & session, const request& req, reply& rep) 1245 { 1246 struct ah _ah; 1247 1248 std::string uname = ""; 1249 std::string upass = ""; 1250 1251 if (!parse_auth_header(req, &_ah)) 1252 { 1253 size_t uPos = req.uri.find("username="); 1254 size_t pPos = req.uri.find("password="); 1255 if ( 1256 (uPos == std::string::npos) || 1257 (pPos == std::string::npos) 1258 ) 1259 { 1260 return 0; 1261 } 1262 uPos += 9; //strlen("username=") 1263 pPos += 9; //strlen("password=") 1264 size_t uEnd = req.uri.find("&", uPos); 1265 size_t pEnd = req.uri.find("&", pPos); 1266 std::string tmpuname; 1267 std::string tmpupass; 1268 if (uEnd == std::string::npos) 1269 tmpuname = req.uri.substr(uPos); 1270 else 1271 tmpuname = req.uri.substr(uPos, uEnd - uPos); 1272 if (pEnd == std::string::npos) 1273 tmpupass = req.uri.substr(pPos); 1274 else 1275 tmpupass = req.uri.substr(pPos, pEnd - pPos); 1276 if (request_handler::url_decode(tmpuname, uname)) 1277 { 1278 if (request_handler::url_decode(tmpupass, upass)) 1279 { 1280 uname = base64_decode(uname); 1281 upass = GenerateMD5Hash(base64_decode(upass)); 1282 1283 std::vector<_tWebUserPassword>::iterator itt; 1284 for (itt = myWebem->m_userpasswords.begin(); itt != myWebem->m_userpasswords.end(); ++itt) 1285 { 1286 if (itt->Username == uname) 1287 { 1288 if (itt->Password != upass) 1289 { 1290 m_failcounter++; 1291 return 0; 1292 } 1293 session.isnew = true; 1294 session.username = itt->Username; 1295 session.rights = itt->userrights; 1296 session.rememberme = false; 1297 m_failcounter = 0; 1298 return 1; 1299 } 1300 } 1301 } 1302 } 1303 m_failcounter++; 1304 return 0; 1305 } 1306 1307 std::vector<_tWebUserPassword>::iterator itt; 1308 for (itt = myWebem->m_userpasswords.begin(); itt != myWebem->m_userpasswords.end(); ++itt) 1309 { 1310 if (itt->Username == _ah.user) 1311 { 1312 int bOK = check_password(&_ah, itt->Password, myWebem->m_DigistRealm); 1313 if (!bOK) 1314 { 1315 m_failcounter++; 1316 return 0; 1317 } 1318 session.isnew = true; 1319 session.username = itt->Username; 1320 session.rights = itt->userrights; 1321 session.rememberme = false; 1322 m_failcounter = 0; 1323 return 1; 1324 } 1325 } 1326 m_failcounter++; 1327 return 0; 1328 } 1329 IsIPInRange(const std::string & ip,const _tIPNetwork & ipnetwork)1330 bool IsIPInRange(const std::string &ip, const _tIPNetwork &ipnetwork) 1331 { 1332 if (ipnetwork.hostname.size() != 0) 1333 { 1334 return (ip == ipnetwork.hostname); 1335 } 1336 uint32_t ip_addr = IPToUInt(ip); 1337 if (ip_addr == 0) 1338 return false; 1339 1340 uint32_t net_lower = (ipnetwork.network & ipnetwork.mask); 1341 uint32_t net_upper = (net_lower | (~ipnetwork.mask)); 1342 1343 if (ip_addr >= net_lower && 1344 ip_addr <= net_upper) 1345 return true; 1346 return false; 1347 } 1348 1349 //Returns true is the connected host is in the local network AreWeInLocalNetwork(const std::string & sHost,const request & req)1350 bool cWebemRequestHandler::AreWeInLocalNetwork(const std::string &sHost, const request& req) 1351 { 1352 //check if in local network(s) 1353 if (myWebem->m_localnetworks.size() == 0) 1354 return false; 1355 if (sHost.size() < 3) 1356 return false; 1357 1358 std::vector<_tIPNetwork>::const_iterator itt; 1359 1360 /* RK, this doesn't work with IPv6 addresses. 1361 pos=host.find_first_of(":"); 1362 if (pos!=std::string::npos) 1363 host=host.substr(0,pos); 1364 */ 1365 1366 for (itt = myWebem->m_localnetworks.begin(); itt != myWebem->m_localnetworks.end(); ++itt) 1367 { 1368 if (IsIPInRange(sHost, *itt)) 1369 { 1370 return true; 1371 } 1372 } 1373 return false; 1374 } 1375 1376 const char * months[12] = { 1377 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 1378 }; 1379 1380 const char * wkdays[7] = { 1381 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 1382 }; 1383 make_web_time(const time_t rawtime)1384 char *make_web_time(const time_t rawtime) 1385 { 1386 static char buffer[256]; 1387 struct tm gmt; 1388 #ifdef _WIN32 1389 if (gmtime_r(&rawtime, &gmt)) //windows returns errno_t, which returns zero when successful 1390 #else 1391 if (gmtime_r(&rawtime, &gmt) == NULL) 1392 #endif 1393 { 1394 strcpy(buffer, "Thu, 1 Jan 1970 00:00:00 GMT"); 1395 } 1396 else 1397 { 1398 sprintf(buffer, "%s, %02d %s %04d %02d:%02d:%02d GMT", 1399 wkdays[gmt.tm_wday], 1400 gmt.tm_mday, 1401 months[gmt.tm_mon], 1402 gmt.tm_year + 1900, 1403 gmt.tm_hour, 1404 gmt.tm_min, 1405 gmt.tm_sec); 1406 1407 1408 } 1409 return buffer; 1410 } 1411 send_remove_cookie(reply & rep)1412 void cWebemRequestHandler::send_remove_cookie(reply& rep) 1413 { 1414 std::stringstream sstr; 1415 sstr << "DMZSID=none"; 1416 // RK, we removed path=/ so you can be logged in to two Domoticz's at the same time on https://my.domoticz.com/. 1417 sstr << "; HttpOnly; Expires=" << make_web_time(0); 1418 reply::add_header(&rep, "Set-Cookie", sstr.str(), false); 1419 } 1420 generateSessionID()1421 std::string cWebemRequestHandler::generateSessionID() 1422 { 1423 // Session id should not be predictable 1424 std::string randomValue = GenerateUUID(); 1425 1426 std::string sessionId = GenerateMD5Hash(base64_encode(randomValue)); 1427 1428 _log.Debug(DEBUG_WEBSERVER, "[web:%s] generate new session id token %s", myWebem->GetPort().c_str(), sessionId.c_str()); 1429 1430 return sessionId; 1431 } 1432 generateAuthToken(const WebEmSession & session,const request & req)1433 std::string cWebemRequestHandler::generateAuthToken(const WebEmSession & session, const request & req) 1434 { 1435 // Authentication token should not be predictable 1436 std::string randomValue = GenerateUUID(); 1437 1438 std::string authToken = base64_encode(randomValue); 1439 1440 _log.Debug(DEBUG_WEBSERVER, "[web:%s] generate new authentication token %s", myWebem->GetPort().c_str(), authToken.c_str()); 1441 1442 session_store_impl_ptr sstore = myWebem->GetSessionStore(); 1443 if (sstore != NULL) 1444 { 1445 WebEmStoredSession storedSession; 1446 storedSession.id = session.id; 1447 storedSession.auth_token = GenerateMD5Hash(authToken); // only save the hash to avoid a security issue if database is stolen 1448 storedSession.username = session.username; 1449 storedSession.expires = session.expires; 1450 storedSession.remote_host = session.remote_host; // to trace host 1451 sstore->StoreSession(storedSession); // only one place to do that 1452 } 1453 1454 return authToken; 1455 } 1456 send_cookie(reply & rep,const WebEmSession & session)1457 void cWebemRequestHandler::send_cookie(reply& rep, const WebEmSession & session) 1458 { 1459 std::stringstream sstr; 1460 sstr << "DMZSID=" << session.id << "_" << session.auth_token << "." << session.expires; 1461 sstr << "; HttpOnly; path=/; Expires=" << make_web_time(session.expires); 1462 reply::add_header(&rep, "Set-Cookie", sstr.str(), false); 1463 } 1464 send_authorization_request(reply & rep)1465 void cWebemRequestHandler::send_authorization_request(reply& rep) 1466 { 1467 rep = reply::stock_reply(reply::unauthorized); 1468 rep.status = reply::unauthorized; 1469 send_remove_cookie(rep); 1470 if (myWebem->m_authmethod == AUTH_BASIC) 1471 { 1472 char szAuthHeader[200]; 1473 sprintf(szAuthHeader, 1474 "Basic " 1475 "realm=\"%s\"", 1476 myWebem->m_DigistRealm.c_str()); 1477 reply::add_header(&rep, "WWW-Authenticate", szAuthHeader); 1478 } 1479 } 1480 CompressWebOutput(const request & req,reply & rep)1481 bool cWebemRequestHandler::CompressWebOutput(const request& req, reply& rep) 1482 { 1483 if (myWebem->m_gzipmode != WWW_USE_GZIP) 1484 return false; 1485 1486 std::string request_path; 1487 if (!url_decode(req.uri, request_path)) 1488 return false; 1489 if ( 1490 (request_path.find(".png") != std::string::npos) || 1491 (request_path.find(".jpg") != std::string::npos) 1492 ) 1493 { 1494 //don't compress 'compressed' images 1495 return false; 1496 } 1497 1498 const char *encoding_header; 1499 //check gzip support if yes, send it back in gzip format 1500 if ((encoding_header = request::get_req_header(&req, "Accept-Encoding")) != NULL) 1501 { 1502 //see if we support gzip 1503 bool bHaveGZipSupport = (strstr(encoding_header, "gzip") != NULL); 1504 if (bHaveGZipSupport) 1505 { 1506 CA2GZIP gzip((char*)rep.content.c_str(), (int)rep.content.size()); 1507 if ((gzip.Length > 0) && (gzip.Length < (int)rep.content.size())) 1508 { 1509 rep.bIsGZIP = true; // flag for later 1510 rep.content.clear(); 1511 rep.content.append((char*)gzip.pgzip, gzip.Length); 1512 //Set new content length 1513 reply::add_header(&rep, "Content-Length", std::to_string(rep.content.size())); 1514 //Add gzip header 1515 reply::add_header(&rep, "Content-Encoding", "gzip"); 1516 return true; 1517 } 1518 } 1519 } 1520 return false; 1521 } 1522 compute_accept_header(const std::string & websocket_key)1523 std::string cWebemRequestHandler::compute_accept_header(const std::string &websocket_key) 1524 { 1525 // the length of an sha1 hash 1526 const int sha1len = 20; 1527 // the GUID as specified in RFC 6455 1528 const char *GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 1529 std::string combined = websocket_key + GUID; 1530 unsigned char sha1result[sha1len]; 1531 sha1::calc((void *)combined.c_str(), combined.length(), sha1result); 1532 std::string accept = base64_encode(sha1result, sha1len); 1533 return accept; 1534 } 1535 is_upgrade_request(WebEmSession & session,const request & req,reply & rep)1536 bool cWebemRequestHandler::is_upgrade_request(WebEmSession & session, const request& req, reply& rep) 1537 { 1538 // request method should be GET 1539 if (req.method != "GET") 1540 { 1541 return false; 1542 } 1543 // http version should be 1.1 at least 1544 if (((req.http_version_major * 10) + req.http_version_minor) < 11) 1545 { 1546 return false; 1547 } 1548 const char *h; 1549 // client MUST include Connection: Upgrade header 1550 h = request::get_req_header(&req, "Connection"); 1551 if (!h) 1552 { 1553 return false; 1554 } 1555 1556 /* 1557 std::string connection_header = h; 1558 if (!boost::iequals(connection_header, "upgrade")) 1559 { 1560 return false; 1561 }; 1562 */ 1563 // client MUST include Upgrade: websocket 1564 h = request::get_req_header(&req, "Upgrade"); 1565 if (!h) 1566 { 1567 return false; 1568 } 1569 1570 if (!CheckAuthentication(session, req, rep)) 1571 return false; 1572 1573 1574 std::string upgrade_header = h; 1575 if (!boost::iequals(upgrade_header, "websocket")) 1576 { 1577 return false; 1578 }; 1579 // we only have one service until now 1580 if (req.uri.find("/json") == std::string::npos) 1581 { 1582 // todo: request uri could be an absolute URI as well!!! 1583 rep = reply::stock_reply(reply::not_found); 1584 return true; 1585 } 1586 h = request::get_req_header(&req, "Host"); 1587 // request MUST include a host header, even if we don't check it 1588 if (h == NULL) 1589 { 1590 rep = reply::stock_reply(reply::forbidden); 1591 return true; 1592 } 1593 h = request::get_req_header(&req, "Origin"); 1594 // request MUST include an origin header, even if we don't check it 1595 // we only "allow" connections from browser clients 1596 if (h == NULL) 1597 { 1598 rep = reply::stock_reply(reply::forbidden); 1599 return true; 1600 } 1601 h = request::get_req_header(&req, "Sec-Websocket-Version"); 1602 // request MUST include a version number 1603 if (h == NULL) 1604 { 1605 rep = reply::stock_reply(reply::internal_server_error); 1606 return true; 1607 } 1608 int version = atoi(h); 1609 // we support versions 13 (and higher) 1610 if (version < 13) 1611 { 1612 rep = reply::stock_reply(reply::internal_server_error); 1613 return true; 1614 } 1615 h = request::get_req_header(&req, "Sec-Websocket-Protocol"); 1616 // check if a protocol is given, and it includes the {websocket_protocol}. 1617 if (!h) 1618 { 1619 return false; 1620 } 1621 std::string protocol_header = h; 1622 if (protocol_header.find(websocket_protocol) == std::string::npos) 1623 { 1624 rep = reply::stock_reply(reply::internal_server_error); 1625 return true; 1626 } 1627 h = request::get_req_header(&req, "Sec-Websocket-Key"); 1628 // request MUST include a sec-websocket-key header and we need to respond to it 1629 if (h == NULL) 1630 { 1631 rep = reply::stock_reply(reply::internal_server_error); 1632 return true; 1633 } 1634 std::string websocket_key = h; 1635 rep = reply::stock_reply(reply::switching_protocols); 1636 reply::add_header(&rep, "Connection", "Upgrade"); 1637 reply::add_header(&rep, "Upgrade", "websocket"); 1638 1639 std::string accept = compute_accept_header(websocket_key); 1640 if (accept.empty()) 1641 { 1642 rep = reply::stock_reply(reply::internal_server_error); 1643 return true; 1644 } 1645 reply::add_header(&rep, "Sec-Websocket-Accept", accept); 1646 // we only speak the {websocket_protocol} subprotocol 1647 reply::add_header(&rep, "Sec-Websocket-Protocol", websocket_protocol); 1648 return true; 1649 } 1650 GetURICommandParameter(const std::string & uri,std::string & cmdparam)1651 static bool GetURICommandParameter(const std::string &uri, std::string &cmdparam) 1652 { 1653 if (uri.find("type=command") == std::string::npos) 1654 return false; 1655 size_t ppos1 = uri.find("¶m="); 1656 size_t ppos2 = uri.find("?param="); 1657 if ( 1658 (ppos1 == std::string::npos) && 1659 (ppos2 == std::string::npos) 1660 ) 1661 return false; 1662 cmdparam = uri; 1663 size_t ppos = ppos1; 1664 if (ppos == std::string::npos) 1665 ppos = ppos2; 1666 else 1667 { 1668 if ((ppos2 < ppos) && (ppos != std::string::npos)) 1669 ppos = ppos2; 1670 } 1671 cmdparam = uri.substr(ppos + 7); 1672 ppos = cmdparam.find("&"); 1673 if (ppos != std::string::npos) 1674 { 1675 cmdparam = cmdparam.substr(0, ppos); 1676 } 1677 return true; 1678 } 1679 CheckAuthentication(WebEmSession & session,const request & req,reply & rep)1680 bool cWebemRequestHandler::CheckAuthentication(WebEmSession & session, const request& req, reply& rep) 1681 { 1682 session.rights = -1; // no rights 1683 session.id = ""; 1684 1685 if (myWebem->m_userpasswords.size() == 0) 1686 { 1687 session.rights = 2; 1688 } 1689 1690 if (AreWeInLocalNetwork(session.remote_host, req)) 1691 { 1692 session.rights = 2; 1693 } 1694 1695 //Check cookie if still valid 1696 const char* cookie_header = request::get_req_header(&req, "Cookie"); 1697 if (cookie_header != NULL) 1698 { 1699 std::string sSID; 1700 std::string sAuthToken; 1701 std::string szTime; 1702 bool expired = false; 1703 1704 // Parse session id and its expiration date 1705 std::string scookie = cookie_header; 1706 size_t fpos = scookie.find("DMZSID="); 1707 if (fpos != std::string::npos) 1708 { 1709 scookie = scookie.substr(fpos); 1710 fpos = 0; 1711 size_t epos = scookie.find(';'); 1712 if (epos != std::string::npos) 1713 { 1714 scookie = scookie.substr(0, epos); 1715 } 1716 } 1717 size_t upos = scookie.find("_", fpos); 1718 size_t ppos = scookie.find(".", upos); 1719 time_t now = mytime(NULL); 1720 if ((fpos != std::string::npos) && (upos != std::string::npos) && (ppos != std::string::npos)) 1721 { 1722 sSID = scookie.substr(fpos + 7, upos - fpos - 7); 1723 sAuthToken = scookie.substr(upos + 1, ppos - upos - 1); 1724 szTime = scookie.substr(ppos + 1); 1725 1726 time_t stime; 1727 std::stringstream sstr; 1728 sstr << szTime; 1729 sstr >> stime; 1730 1731 expired = stime < now; 1732 } 1733 1734 if (session.rights == 2) 1735 { 1736 if (!sSID.empty()) 1737 { 1738 WebEmSession* oldSession = myWebem->GetSession(sSID); 1739 if (oldSession == NULL) 1740 { 1741 session.id = sSID; 1742 session.auth_token = sAuthToken; 1743 expired = (!checkAuthToken(session)); 1744 } 1745 else 1746 { 1747 session = *oldSession; 1748 expired = (oldSession->expires < now); 1749 } 1750 } 1751 if (sSID.empty() || expired) 1752 session.isnew = true; 1753 return true; 1754 } 1755 1756 if (!(sSID.empty() || sAuthToken.empty() || szTime.empty())) 1757 { 1758 WebEmSession* oldSession = myWebem->GetSession(sSID); 1759 if ((oldSession != NULL) && (oldSession->expires < now)) 1760 { 1761 // Check if session stored in memory is not expired (prevent from spoofing expiration time) 1762 expired = true; 1763 } 1764 if (expired) 1765 { 1766 //expired session, remove session 1767 m_failcounter = 0; 1768 if (oldSession != NULL) 1769 { 1770 // session exists (delete it from memory and database) 1771 myWebem->RemoveSession(sSID); 1772 removeAuthToken(sSID); 1773 } 1774 send_authorization_request(rep); 1775 return false; 1776 } 1777 if (oldSession != NULL) 1778 { 1779 // session already exists 1780 session = *oldSession; 1781 } 1782 else 1783 { 1784 // Session does not exists 1785 session.id = sSID; 1786 } 1787 session.auth_token = sAuthToken; 1788 // Check authen_token and restore session 1789 if (checkAuthToken(session)) 1790 { 1791 // user is authenticated 1792 return true; 1793 } 1794 1795 send_authorization_request(rep); 1796 return false; 1797 1798 } 1799 else 1800 { 1801 // invalid cookie 1802 if (myWebem->m_authmethod != AUTH_BASIC) 1803 { 1804 //Check if we need to bypass authentication (not when using basic-auth) 1805 for (auto itt : myWebem->myWhitelistURLs) 1806 { 1807 if (req.uri.find(itt) == 0) 1808 { 1809 return true; 1810 } 1811 } 1812 1813 std::string cmdparam; 1814 if (GetURICommandParameter(req.uri, cmdparam)) 1815 { 1816 for (auto itt : myWebem->myWhitelistCommands) 1817 { 1818 if (cmdparam.find(itt) == 0) 1819 return true; 1820 } 1821 } 1822 // Force login form 1823 send_authorization_request(rep); 1824 return false; 1825 } 1826 } 1827 } 1828 1829 if ((session.rights == 2) && (session.id.empty())) 1830 { 1831 session.isnew = true; 1832 return true; 1833 } 1834 1835 //patch to let always support basic authentication function for script calls 1836 if (req.uri.find("json.htm") != std::string::npos) 1837 { 1838 //Check first if we have a basic auth 1839 if (authorize(session, req, rep)) 1840 { 1841 return true; 1842 } 1843 } 1844 1845 if (myWebem->m_authmethod == AUTH_BASIC) 1846 { 1847 if (!authorize(session, req, rep)) 1848 { 1849 if (m_failcounter > 0) 1850 { 1851 _log.Log(LOG_ERROR, "[web:%s] Failed authentication attempt, ignoring client request (remote address: %s)", myWebem->GetPort().c_str(), session.remote_host.c_str()); 1852 } 1853 if (m_failcounter > 2) 1854 { 1855 m_failcounter = 0; 1856 rep = reply::stock_reply(reply::service_unavailable); 1857 return false; 1858 } 1859 send_authorization_request(rep); 1860 return false; 1861 } 1862 return true; 1863 } 1864 1865 //Check if we need to bypass authentication (not when using basic-auth) 1866 for (auto itt : myWebem->myWhitelistURLs) 1867 { 1868 if (req.uri.find(itt) == 0) 1869 { 1870 return true; 1871 } 1872 } 1873 std::string cmdparam; 1874 if (GetURICommandParameter(req.uri, cmdparam)) 1875 { 1876 for (auto itt : myWebem->myWhitelistCommands) 1877 { 1878 if (cmdparam.find(itt) == 0) 1879 { 1880 return true; 1881 } 1882 } 1883 } 1884 1885 send_authorization_request(rep); 1886 return false; 1887 } 1888 1889 /** 1890 * Check authentication token if exists and restore the user session if necessary 1891 */ checkAuthToken(WebEmSession & session)1892 bool cWebemRequestHandler::checkAuthToken(WebEmSession & session) 1893 { 1894 session_store_impl_ptr sstore = myWebem->GetSessionStore(); 1895 if (sstore == NULL) 1896 { 1897 _log.Log(LOG_ERROR, "CheckAuthToken([%s_%s]) : no store defined", session.id.c_str(), session.auth_token.c_str()); 1898 return true; 1899 } 1900 1901 if (session.id.empty() || session.auth_token.empty()) 1902 { 1903 _log.Log(LOG_ERROR, "CheckAuthToken(%s_%s) : session id or auth token is empty", session.id.c_str(), session.auth_token.c_str()); 1904 return false; 1905 } 1906 WebEmStoredSession storedSession = sstore->GetSession(session.id); 1907 if (storedSession.id.empty()) 1908 { 1909 _log.Log(LOG_ERROR, "CheckAuthToken(%s_%s) : session id not found", session.id.c_str(), session.auth_token.c_str()); 1910 return false; 1911 } 1912 if (storedSession.auth_token != GenerateMD5Hash(session.auth_token)) 1913 { 1914 _log.Log(LOG_ERROR, "CheckAuthToken(%s_%s) : auth token mismatch", session.id.c_str(), session.auth_token.c_str()); 1915 removeAuthToken(session.id); 1916 return false; 1917 } 1918 1919 _log.Debug(DEBUG_WEBSERVER, "[web:%s] CheckAuthToken(%s_%s_%s) : user authenticated", myWebem->GetPort().c_str(), session.id.c_str(), session.auth_token.c_str(), session.username.c_str()); 1920 1921 if (session.rights == 2) 1922 { 1923 // we are already admin - restore session from db 1924 session.expires = storedSession.expires; 1925 time_t now = mytime(NULL); 1926 if (session.expires < now) 1927 { 1928 removeAuthToken(session.id); 1929 return false; 1930 } 1931 else 1932 { 1933 session.timeout = now + SHORT_SESSION_TIMEOUT; 1934 myWebem->AddSession(session); 1935 return true; 1936 } 1937 } 1938 1939 if (session.username.empty()) 1940 { 1941 // Restore session if user exists and session does not already exist 1942 bool userExists = false; 1943 bool sessionExpires = false; 1944 session.username = storedSession.username; 1945 session.expires = storedSession.expires; 1946 std::vector<_tWebUserPassword>::iterator ittu; 1947 for (ittu = myWebem->m_userpasswords.begin(); ittu != myWebem->m_userpasswords.end(); ++ittu) 1948 { 1949 if (ittu->Username == session.username) // the user still exists 1950 { 1951 userExists = true; 1952 session.rights = ittu->userrights; 1953 break; 1954 } 1955 } 1956 1957 time_t now = mytime(NULL); 1958 sessionExpires = session.expires < now; 1959 1960 if (!userExists || sessionExpires) 1961 { 1962 _log.Debug(DEBUG_WEBSERVER, "[web:%s] CheckAuthToken(%s_%s) : cannot restore session, user not found or session expired", myWebem->GetPort().c_str(), session.id.c_str(), session.auth_token.c_str()); 1963 removeAuthToken(session.id); 1964 return false; 1965 } 1966 1967 WebEmSession* oldSession = myWebem->GetSession(session.id); 1968 if (oldSession == NULL) 1969 { 1970 _log.Debug(DEBUG_WEBSERVER, "[web:%s] CheckAuthToken(%s_%s_%s) : restore session", myWebem->GetPort().c_str(), session.id.c_str(), session.auth_token.c_str(), session.username.c_str()); 1971 myWebem->AddSession(session); 1972 } 1973 } 1974 1975 return true; 1976 } 1977 removeAuthToken(const std::string & sessionId)1978 void cWebemRequestHandler::removeAuthToken(const std::string & sessionId) 1979 { 1980 session_store_impl_ptr sstore = myWebem->GetSessionStore(); 1981 if (sstore != NULL) 1982 { 1983 sstore->RemoveSession(sessionId); 1984 } 1985 } 1986 strftime_t(const char * format,const time_t rawtime)1987 char *cWebemRequestHandler::strftime_t(const char *format, const time_t rawtime) 1988 { 1989 static char buffer[1024]; 1990 struct tm ltime; 1991 localtime_r(&rawtime, <ime); 1992 strftime(buffer, sizeof(buffer), format, <ime); 1993 return buffer; 1994 } 1995 handle_request(const request & req,reply & rep)1996 void cWebemRequestHandler::handle_request(const request& req, reply& rep) 1997 { 1998 _log.Debug(DEBUG_WEBSERVER, "web: Host:%s Uri;%s", req.host_address.c_str(), req.uri.c_str()); 1999 2000 // Initialize session 2001 WebEmSession session; 2002 session.remote_host = req.host_address; 2003 2004 if (myWebem->myRemoteProxyIPs.size() > 0) 2005 { 2006 for (std::vector<std::string>::size_type i = 0; i < myWebem->myRemoteProxyIPs.size(); ++i) 2007 { 2008 if (session.remote_host == myWebem->myRemoteProxyIPs[i]) 2009 { 2010 const char *host_header = request::get_req_header(&req, "X-Forwarded-For"); 2011 if (host_header != NULL) 2012 { 2013 if (strstr(host_header, ",") != NULL) 2014 { 2015 //Multiple proxies are used... this is not very common 2016 host_header = request::get_req_header(&req, "X-Real-IP"); //try our NGINX header 2017 if (!host_header) 2018 { 2019 _log.Log(LOG_ERROR, "Webserver: Multiple proxies are used (Or possible spoofing attempt), ignoring client request (remote address: %s)", session.remote_host.c_str()); 2020 rep = reply::stock_reply(reply::forbidden); 2021 return; 2022 } 2023 } 2024 session.remote_host = host_header; 2025 } 2026 } 2027 } 2028 } 2029 2030 session.reply_status = reply::ok; 2031 session.isnew = false; 2032 session.forcelogin = false; 2033 session.rememberme = false; 2034 2035 rep.status = reply::ok; 2036 rep.bIsGZIP = false; 2037 2038 bool isPage = myWebem->IsPageOverride(req, rep); 2039 bool isAction = myWebem->IsAction(req); 2040 2041 // Respond to CORS Preflight request (for JSON API) 2042 if (req.method == "OPTIONS") 2043 { 2044 reply::add_header(&rep, "Content-Length", "0"); 2045 reply::add_header(&rep, "Content-Type", "text/plain"); 2046 reply::add_header(&rep, "Access-Control-Max-Age", "3600"); 2047 reply::add_header(&rep, "Access-Control-Allow-Origin", "*"); 2048 reply::add_header(&rep, "Access-Control-Allow-Methods", "GET, POST"); 2049 reply::add_header(&rep, "Access-Control-Allow-Headers", "Authorization, Content-Type"); 2050 return; 2051 } 2052 2053 // Check authentication on each page or action, if it exists. 2054 bool bCheckAuthentication = false; 2055 if (isPage || isAction) 2056 { 2057 bCheckAuthentication = true; 2058 } 2059 2060 if (isPage && (req.uri.find("dologout") != std::string::npos)) 2061 { 2062 //Remove session id based on cookie 2063 const char *cookie; 2064 cookie = request::get_req_header(&req, "Cookie"); 2065 if (cookie != NULL) 2066 { 2067 std::string scookie = cookie; 2068 size_t fpos = scookie.find("DMZSID="); 2069 size_t upos = scookie.find("_", fpos); 2070 if ((fpos != std::string::npos) && (upos != std::string::npos)) 2071 { 2072 std::string sSID = scookie.substr(fpos + 7, upos - fpos - 7); 2073 _log.Debug(DEBUG_WEBSERVER, "Web: Logout : remove session %s", sSID.c_str()); 2074 std::map<std::string, WebEmSession>::iterator itt = myWebem->m_sessions.find(sSID); 2075 if (itt != myWebem->m_sessions.end()) 2076 { 2077 myWebem->m_sessions.erase(itt); 2078 } 2079 removeAuthToken(sSID); 2080 } 2081 } 2082 session.username = ""; 2083 session.rights = -1; 2084 session.forcelogin = true; 2085 bCheckAuthentication = false; // do not authenticate the user, just logout 2086 send_authorization_request(rep); 2087 return; 2088 } 2089 2090 // Check if this is an upgrade request to a websocket connection 2091 if (is_upgrade_request(session, req, rep)) 2092 { 2093 return; 2094 } 2095 // Check user authentication on each page or action, if it exists. 2096 if ((isPage || isAction || bCheckAuthentication) && !CheckAuthentication(session, req, rep)) 2097 { 2098 return; 2099 } 2100 2101 // Copy the request to be able to fill its parameters attribute 2102 request requestCopy = req; 2103 2104 bool bHandledAction = false; 2105 // Run action if exists 2106 if (isAction) 2107 { 2108 // Post actions only allowed when authenticated and user has admin rights 2109 if (session.rights != 2) 2110 { 2111 rep = reply::stock_reply(reply::forbidden); 2112 return; 2113 } 2114 bHandledAction = myWebem->CheckForAction(session, requestCopy); 2115 if (!requestCopy.uri.empty()) 2116 { 2117 if ((requestCopy.method == "POST") && (requestCopy.uri[0] != '/')) 2118 { 2119 //Send back as data instead of a redirect uri 2120 rep.status = reply::ok; 2121 rep.content = requestCopy.uri; 2122 reply::add_header(&rep, "Content-Length", std::to_string(rep.content.size())); 2123 reply::add_header(&rep, "Last-Modified", make_web_time(mytime(NULL)), true); 2124 reply::add_header(&rep, "Content-Type", "application/json;charset=UTF-8"); 2125 return; 2126 } 2127 } 2128 } 2129 2130 if (!bHandledAction) 2131 { 2132 if (myWebem->CheckForPageOverride(session, requestCopy, rep)) 2133 { 2134 if (rep.status == reply::status_type::download_file) 2135 return; 2136 2137 if (session.reply_status != reply::ok) // forbidden 2138 { 2139 rep = reply::stock_reply(static_cast<reply::status_type>(session.reply_status)); 2140 return; 2141 } 2142 2143 if (!rep.bIsGZIP) 2144 { 2145 CompressWebOutput(req, rep); 2146 } 2147 } 2148 else 2149 { 2150 modify_info mInfo; 2151 if (session.reply_status != reply::ok) 2152 { 2153 rep = reply::stock_reply(static_cast<reply::status_type>(session.reply_status)); 2154 return; 2155 } 2156 if (rep.status != reply::ok) // bad request 2157 { 2158 return; 2159 } 2160 2161 // do normal handling 2162 try 2163 { 2164 std::string uri = myWebem->ExtractRequestPath(requestCopy.uri); 2165 if (uri.find("/images/") == 0) 2166 { 2167 std::string theme_images_path = myWebem->m_actTheme + uri; 2168 if (file_exist((doc_root_ + theme_images_path).c_str())) 2169 requestCopy.uri = myWebem->GetWebRoot() + theme_images_path; 2170 } 2171 2172 request_handler::handle_request(requestCopy, rep, mInfo); 2173 } 2174 catch (...) 2175 { 2176 rep = reply::stock_reply(reply::internal_server_error); 2177 return; 2178 } 2179 2180 // find content type header 2181 std::string content_type; 2182 for (unsigned int h = 0; h < rep.headers.size(); h++) 2183 { 2184 if (boost::iequals(rep.headers[h].name, "Content-Type")) 2185 { 2186 content_type = rep.headers[h].value; 2187 break; 2188 } 2189 } 2190 2191 if (content_type == "text/html" 2192 || content_type == "text/plain" 2193 || content_type == "text/css" 2194 || content_type == "text/javascript" 2195 || content_type == "application/javascript" 2196 ) 2197 { 2198 // check if content is not gzipped, include won't work with non-text content 2199 if (!rep.bIsGZIP) 2200 { 2201 // Find and include any special cWebem strings 2202 if (!myWebem->Include(rep.content)) 2203 { 2204 if (mInfo.mtime_support && !mInfo.is_modified) 2205 { 2206 _log.Debug(DEBUG_WEBSERVER, "[web:%s] %s not modified (1).", myWebem->GetPort().c_str(), req.uri.c_str()); 2207 rep = reply::stock_reply(reply::not_modified); 2208 return; 2209 } 2210 } 2211 2212 // adjust content length header 2213 // ( Firefox ignores this, but apparently some browsers truncate display without it. 2214 // fix provided by http://www.codeproject.com/Members/jaeheung72 ) 2215 2216 reply::add_header(&rep, "Content-Length", std::to_string(rep.content.size())); 2217 2218 if (!mInfo.mtime_support) 2219 { 2220 reply::add_header(&rep, "Last-Modified", make_web_time(mytime(NULL)), true); 2221 } 2222 2223 //check gzip support if yes, send it back in gzip format 2224 CompressWebOutput(req, rep); 2225 } 2226 2227 // tell browser that we are using UTF-8 encoding 2228 reply::add_header(&rep, "Content-Type", content_type + ";charset=UTF-8"); 2229 } 2230 else if (mInfo.mtime_support && !mInfo.is_modified) 2231 { 2232 rep = reply::stock_reply(reply::not_modified); 2233 _log.Debug(DEBUG_WEBSERVER, "[web:%s] %s not modified (2).", myWebem->GetPort().c_str(), req.uri.c_str()); 2234 return; 2235 } 2236 else if (content_type.find("image/") != std::string::npos) 2237 { 2238 //Cache images 2239 reply::add_header(&rep, "Expires", make_web_time(mytime(NULL) + 3600 * 24 * 365)); // one year 2240 } 2241 else 2242 { 2243 // tell browser that we are using UTF-8 encoding 2244 reply::add_header(&rep, "Content-Type", content_type + ";charset=UTF-8"); 2245 } 2246 } 2247 } 2248 2249 // Set timeout to make session in use 2250 session.timeout = mytime(NULL) + SHORT_SESSION_TIMEOUT; 2251 2252 if ((session.isnew == true) && 2253 (session.rights == 2) && 2254 (req.uri.find("json.htm") != std::string::npos) && 2255 (req.uri.find("logincheck") == std::string::npos) 2256 ) 2257 { 2258 // client is possibly a script that does not send cookies - see if we have the IP address registered as a session ID 2259 WebEmSession* memSession = myWebem->GetSession(session.remote_host); 2260 time_t now = mytime(NULL); 2261 if (memSession != NULL) 2262 { 2263 if (memSession->expires < now) 2264 { 2265 myWebem->RemoveSession(session.remote_host); 2266 } 2267 else 2268 { 2269 session.isnew = false; 2270 if (memSession->expires - (SHORT_SESSION_TIMEOUT / 2) < now) 2271 { 2272 memSession->expires = now + SHORT_SESSION_TIMEOUT; 2273 2274 // unsure about the point of the forced removal of 'live' sessions and restore from 2275 // database but these 'fake' sessions are memory only and can't be restored that way. 2276 // Should I do a RemoveSession() followed by a AddSession()? 2277 // For now: keep 'timeout' in sync with 'expires' 2278 memSession->timeout = memSession->expires; 2279 } 2280 } 2281 } 2282 2283 if (session.isnew == true) 2284 { 2285 // register a 'fake' IP based session so we can reference that if the client returns here 2286 session.id = session.remote_host; 2287 session.rights = -1; // predictable session ID must have no rights 2288 session.expires = session.timeout; 2289 myWebem->AddSession(session); 2290 session.rights = 2; // restore session rights 2291 } 2292 } 2293 2294 if (session.isnew == true) 2295 { 2296 _log.Log(LOG_STATUS, "Incoming connection from: %s", session.remote_host.c_str()); 2297 // Create a new session ID 2298 session.id = generateSessionID(); 2299 session.expires = session.timeout; 2300 if (session.rememberme) 2301 { 2302 // Extend session by 30 days 2303 session.expires += LONG_SESSION_TIMEOUT; 2304 } 2305 session.auth_token = generateAuthToken(session, req); // do it after expires to save it also 2306 session.isnew = false; 2307 myWebem->AddSession(session); 2308 send_cookie(rep, session); 2309 } 2310 else if (session.forcelogin == true) 2311 { 2312 _log.Debug(DEBUG_WEBSERVER, "[web:%s] Logout : remove session %s", myWebem->GetPort().c_str(), session.id.c_str()); 2313 2314 myWebem->RemoveSession(session.id); 2315 removeAuthToken(session.id); 2316 if (myWebem->m_authmethod == AUTH_BASIC) 2317 { 2318 send_authorization_request(rep); 2319 } 2320 2321 } 2322 else if (session.id.size() > 0) 2323 { 2324 // Renew session expiration and authentication token 2325 WebEmSession* memSession = myWebem->GetSession(session.id); 2326 if (memSession != NULL) 2327 { 2328 time_t now = mytime(NULL); 2329 // Renew session expiration date if half of session duration has been exceeded ("dont remember me" sessions, 10 minutes) 2330 if (memSession->expires - (SHORT_SESSION_TIMEOUT / 2) < now) 2331 { 2332 memSession->expires = now + SHORT_SESSION_TIMEOUT; 2333 memSession->auth_token = generateAuthToken(*memSession, req); // do it after expires to save it also 2334 send_cookie(rep, *memSession); 2335 } 2336 // Renew session expiration date if half of session duration has been exceeded ("remember me" sessions, 30 days) 2337 else if ((memSession->expires > SHORT_SESSION_TIMEOUT + now) && (memSession->expires - (LONG_SESSION_TIMEOUT / 2) < now)) 2338 { 2339 memSession->expires = now + LONG_SESSION_TIMEOUT; 2340 memSession->auth_token = generateAuthToken(*memSession, req); // do it after expires to save it also 2341 send_cookie(rep, *memSession); 2342 } 2343 } 2344 } 2345 } 2346 2347 } //namespace server { 2348 } //namespace http { 2349