1 #pragma once 2 3 #include "async_resp.hpp" 4 #include "common.hpp" 5 #include "dbus_utility.hpp" 6 #include "error_messages.hpp" 7 #include "http_request.hpp" 8 #include "http_response.hpp" 9 #include "logging.hpp" 10 #include "privileges.hpp" 11 #include "sessions.hpp" 12 #include "utility.hpp" 13 #include "utils/dbus_utils.hpp" 14 #include "verb.hpp" 15 #include "websocket.hpp" 16 17 #include <boost/beast/ssl/ssl_stream.hpp> 18 #include <boost/container/flat_map.hpp> 19 #include <sdbusplus/unpack_properties.hpp> 20 21 #include <cerrno> 22 #include <cstdint> 23 #include <cstdlib> 24 #include <limits> 25 #include <memory> 26 #include <optional> 27 #include <tuple> 28 #include <utility> 29 #include <vector> 30 31 namespace crow 32 { 33 34 class BaseRule 35 { 36 public: 37 explicit BaseRule(const std::string& thisRule) : rule(thisRule) 38 {} 39 40 virtual ~BaseRule() = default; 41 42 BaseRule(const BaseRule&) = delete; 43 BaseRule(BaseRule&&) = delete; 44 BaseRule& operator=(const BaseRule&) = delete; 45 BaseRule& operator=(const BaseRule&&) = delete; 46 47 virtual void validate() = 0; 48 std::unique_ptr<BaseRule> upgrade() 49 { 50 if (ruleToUpgrade) 51 { 52 return std::move(ruleToUpgrade); 53 } 54 return {}; 55 } 56 57 virtual void handle(const Request& /*req*/, 58 const std::shared_ptr<bmcweb::AsyncResp>&, 59 const std::vector<std::string>&) = 0; 60 #ifndef BMCWEB_ENABLE_SSL 61 virtual void 62 handleUpgrade(const Request& /*req*/, 63 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 64 boost::asio::ip::tcp::socket&& /*adaptor*/) 65 { 66 asyncResp->res.result(boost::beast::http::status::not_found); 67 } 68 #else 69 virtual void handleUpgrade( 70 const Request& /*req*/, 71 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 72 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& /*adaptor*/) 73 { 74 asyncResp->res.result(boost::beast::http::status::not_found); 75 } 76 #endif 77 78 size_t getMethods() const 79 { 80 return methodsBitfield; 81 } 82 83 bool checkPrivileges(const redfish::Privileges& userPrivileges) 84 { 85 // If there are no privileges assigned, assume no privileges 86 // required 87 if (privilegesSet.empty()) 88 { 89 return true; 90 } 91 92 for (const redfish::Privileges& requiredPrivileges : privilegesSet) 93 { 94 if (userPrivileges.isSupersetOf(requiredPrivileges)) 95 { 96 return true; 97 } 98 } 99 return false; 100 } 101 102 size_t methodsBitfield{1 << static_cast<size_t>(HttpVerb::Get)}; 103 static_assert(std::numeric_limits<decltype(methodsBitfield)>::digits > 104 methodNotAllowedIndex, 105 "Not enough bits to store bitfield"); 106 107 std::vector<redfish::Privileges> privilegesSet; 108 109 std::string rule; 110 std::string nameStr; 111 112 std::unique_ptr<BaseRule> ruleToUpgrade; 113 114 friend class Router; 115 template <typename T> 116 friend struct RuleParameterTraits; 117 }; 118 119 namespace detail 120 { 121 namespace routing_handler_call_helper 122 { 123 template <typename T, int Pos> 124 struct CallPair 125 { 126 using type = T; 127 static const int pos = Pos; 128 }; 129 130 template <typename H1> 131 struct CallParams 132 { 133 H1& handler; 134 const std::vector<std::string>& params; 135 const Request& req; 136 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp; 137 }; 138 139 template <typename F, int NString, typename S1, typename S2> 140 struct Call 141 {}; 142 143 template <typename F, int NString, typename... Args1, typename... Args2> 144 struct Call<F, NString, black_magic::S<std::string, Args1...>, 145 black_magic::S<Args2...>> 146 { 147 void operator()(F cparams) 148 { 149 using pushed = typename black_magic::S<Args2...>::template push_back< 150 CallPair<std::string, NString>>; 151 Call<F, NString + 1, black_magic::S<Args1...>, pushed>()(cparams); 152 } 153 }; 154 155 template <typename F, int NString, typename... Args1> 156 struct Call<F, NString, black_magic::S<>, black_magic::S<Args1...>> 157 { 158 void operator()(F cparams) 159 { 160 cparams.handler(cparams.req, cparams.asyncResp, 161 cparams.params[Args1::pos]...); 162 } 163 }; 164 165 template <typename Func, typename... ArgsWrapped> 166 struct Wrapped 167 { 168 template <typename... Args> 169 void set( 170 Func f, 171 typename std::enable_if< 172 !std::is_same< 173 typename std::tuple_element<0, std::tuple<Args..., void>>::type, 174 const Request&>::value, 175 int>::type /*enable*/ 176 = 0) 177 { 178 handler = [f = std::forward<Func>(f)]( 179 const Request&, 180 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 181 Args... args) { asyncResp->res.result(f(args...)); }; 182 } 183 184 template <typename Req, typename... Args> 185 struct ReqHandlerWrapper 186 { 187 explicit ReqHandlerWrapper(Func fIn) : f(std::move(fIn)) 188 {} 189 190 void operator()(const Request& req, 191 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 192 Args... args) 193 { 194 asyncResp->res.result(f(req, args...)); 195 } 196 197 Func f; 198 }; 199 200 template <typename... Args> 201 void set( 202 Func f, 203 typename std::enable_if< 204 std::is_same< 205 typename std::tuple_element<0, std::tuple<Args..., void>>::type, 206 const Request&>::value && 207 !std::is_same<typename std::tuple_element< 208 1, std::tuple<Args..., void, void>>::type, 209 const std::shared_ptr<bmcweb::AsyncResp>&>::value, 210 int>::type /*enable*/ 211 = 0) 212 { 213 handler = ReqHandlerWrapper<Args...>(std::move(f)); 214 /*handler = ( 215 [f = std::move(f)] 216 (const Request& req, Response& res, Args... args){ 217 res.result(f(req, args...)); 218 res.end(); 219 });*/ 220 } 221 222 template <typename... Args> 223 void set( 224 Func f, 225 typename std::enable_if< 226 std::is_same< 227 typename std::tuple_element<0, std::tuple<Args..., void>>::type, 228 const Request&>::value && 229 std::is_same<typename std::tuple_element< 230 1, std::tuple<Args..., void, void>>::type, 231 const std::shared_ptr<bmcweb::AsyncResp>&>::value, 232 int>::type /*enable*/ 233 = 0) 234 { 235 handler = std::move(f); 236 } 237 238 template <typename... Args> 239 struct HandlerTypeHelper 240 { 241 using type = std::function<void( 242 const crow::Request& /*req*/, 243 const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; 244 using args_type = black_magic::S<Args...>; 245 }; 246 247 template <typename... Args> 248 struct HandlerTypeHelper<const Request&, Args...> 249 { 250 using type = std::function<void( 251 const crow::Request& /*req*/, 252 const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; 253 using args_type = black_magic::S<Args...>; 254 }; 255 256 template <typename... Args> 257 struct HandlerTypeHelper<const Request&, 258 const std::shared_ptr<bmcweb::AsyncResp>&, Args...> 259 { 260 using type = std::function<void( 261 const crow::Request& /*req*/, 262 const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; 263 using args_type = black_magic::S<Args...>; 264 }; 265 266 typename HandlerTypeHelper<ArgsWrapped...>::type handler; 267 268 void operator()(const Request& req, 269 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 270 const std::vector<std::string>& params) 271 { 272 detail::routing_handler_call_helper::Call< 273 detail::routing_handler_call_helper::CallParams<decltype(handler)>, 274 0, typename HandlerTypeHelper<ArgsWrapped...>::args_type, 275 black_magic::S<>>()( 276 detail::routing_handler_call_helper::CallParams<decltype(handler)>{ 277 handler, params, req, asyncResp}); 278 } 279 }; 280 } // namespace routing_handler_call_helper 281 } // namespace detail 282 283 class WebSocketRule : public BaseRule 284 { 285 using self_t = WebSocketRule; 286 287 public: 288 explicit WebSocketRule(const std::string& ruleIn) : BaseRule(ruleIn) 289 {} 290 291 void validate() override 292 {} 293 294 void handle(const Request& /*req*/, 295 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 296 const std::vector<std::string>& /*params*/) override 297 { 298 asyncResp->res.result(boost::beast::http::status::not_found); 299 } 300 301 #ifndef BMCWEB_ENABLE_SSL 302 void handleUpgrade(const Request& req, 303 const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, 304 boost::asio::ip::tcp::socket&& adaptor) override 305 { 306 BMCWEB_LOG_DEBUG << "Websocket handles upgrade"; 307 std::shared_ptr< 308 crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>> 309 myConnection = std::make_shared< 310 crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>( 311 req, std::move(adaptor), openHandler, messageHandler, 312 messageExHandler, closeHandler, errorHandler); 313 myConnection->start(); 314 } 315 #else 316 void handleUpgrade(const Request& req, 317 const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, 318 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& 319 adaptor) override 320 { 321 BMCWEB_LOG_DEBUG << "Websocket handles upgrade"; 322 std::shared_ptr<crow::websocket::ConnectionImpl< 323 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>> 324 myConnection = std::make_shared<crow::websocket::ConnectionImpl< 325 boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>( 326 req, std::move(adaptor), openHandler, messageHandler, 327 messageExHandler, closeHandler, errorHandler); 328 myConnection->start(); 329 } 330 #endif 331 332 template <typename Func> 333 self_t& onopen(Func f) 334 { 335 openHandler = f; 336 return *this; 337 } 338 339 template <typename Func> 340 self_t& onmessage(Func f) 341 { 342 messageHandler = f; 343 return *this; 344 } 345 346 template <typename Func> 347 self_t& onmessageex(Func f) 348 { 349 messageExHandler = f; 350 return *this; 351 } 352 353 template <typename Func> 354 self_t& onclose(Func f) 355 { 356 closeHandler = f; 357 return *this; 358 } 359 360 template <typename Func> 361 self_t& onerror(Func f) 362 { 363 errorHandler = f; 364 return *this; 365 } 366 367 protected: 368 std::function<void(crow::websocket::Connection&)> openHandler; 369 std::function<void(crow::websocket::Connection&, const std::string&, bool)> 370 messageHandler; 371 std::function<void(crow::websocket::Connection&, std::string_view, 372 crow::websocket::MessageType type, 373 std::function<void()>&& whenComplete)> 374 messageExHandler; 375 std::function<void(crow::websocket::Connection&, const std::string&)> 376 closeHandler; 377 std::function<void(crow::websocket::Connection&)> errorHandler; 378 }; 379 380 template <typename T> 381 struct RuleParameterTraits 382 { 383 using self_t = T; 384 WebSocketRule& websocket() 385 { 386 self_t* self = static_cast<self_t*>(this); 387 WebSocketRule* p = new WebSocketRule(self->rule); 388 p->privilegesSet = self->privilegesSet; 389 self->ruleToUpgrade.reset(p); 390 return *p; 391 } 392 393 self_t& name(std::string_view name) noexcept 394 { 395 self_t* self = static_cast<self_t*>(this); 396 self->nameStr = name; 397 return *self; 398 } 399 400 self_t& methods(boost::beast::http::verb method) 401 { 402 self_t* self = static_cast<self_t*>(this); 403 std::optional<HttpVerb> verb = httpVerbFromBoost(method); 404 if (verb) 405 { 406 self->methodsBitfield = 1U << static_cast<size_t>(*verb); 407 } 408 return *self; 409 } 410 411 template <typename... MethodArgs> 412 self_t& methods(boost::beast::http::verb method, MethodArgs... argsMethod) 413 { 414 self_t* self = static_cast<self_t*>(this); 415 methods(argsMethod...); 416 std::optional<HttpVerb> verb = httpVerbFromBoost(method); 417 if (verb) 418 { 419 self->methodsBitfield |= 1U << static_cast<size_t>(*verb); 420 } 421 return *self; 422 } 423 424 self_t& notFound() 425 { 426 self_t* self = static_cast<self_t*>(this); 427 self->methodsBitfield = 1U << notFoundIndex; 428 return *self; 429 } 430 431 self_t& methodNotAllowed() 432 { 433 self_t* self = static_cast<self_t*>(this); 434 self->methodsBitfield = 1U << methodNotAllowedIndex; 435 return *self; 436 } 437 438 self_t& privileges( 439 const std::initializer_list<std::initializer_list<const char*>>& p) 440 { 441 self_t* self = static_cast<self_t*>(this); 442 for (const std::initializer_list<const char*>& privilege : p) 443 { 444 self->privilegesSet.emplace_back(privilege); 445 } 446 return *self; 447 } 448 449 template <size_t N, typename... MethodArgs> 450 self_t& privileges(const std::array<redfish::Privileges, N>& p) 451 { 452 self_t* self = static_cast<self_t*>(this); 453 for (const redfish::Privileges& privilege : p) 454 { 455 self->privilegesSet.emplace_back(privilege); 456 } 457 return *self; 458 } 459 }; 460 461 class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule> 462 { 463 public: 464 explicit DynamicRule(const std::string& ruleIn) : BaseRule(ruleIn) 465 {} 466 467 void validate() override 468 { 469 if (!erasedHandler) 470 { 471 throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") + 472 "no handler for url " + rule); 473 } 474 } 475 476 void handle(const Request& req, 477 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 478 const std::vector<std::string>& params) override 479 { 480 erasedHandler(req, asyncResp, params); 481 } 482 483 template <typename Func> 484 void operator()(Func f) 485 { 486 using boost::callable_traits::args_t; 487 constexpr size_t arity = std::tuple_size<args_t<Func>>::value; 488 constexpr auto is = std::make_integer_sequence<unsigned, arity>{}; 489 erasedHandler = wrap(std::move(f), is); 490 } 491 492 // enable_if Arg1 == request && Arg2 == Response 493 // enable_if Arg1 == request && Arg2 != response 494 // enable_if Arg1 != request 495 496 template <typename Func, unsigned... Indices> 497 std::function<void(const Request&, 498 const std::shared_ptr<bmcweb::AsyncResp>&, 499 const std::vector<std::string>&)> 500 wrap(Func f, std::integer_sequence<unsigned, Indices...> /*is*/) 501 { 502 using function_t = crow::utility::FunctionTraits<Func>; 503 504 auto ret = detail::routing_handler_call_helper::Wrapped< 505 Func, typename function_t::template arg<Indices>...>(); 506 ret.template set<typename function_t::template arg<Indices>...>( 507 std::move(f)); 508 return ret; 509 } 510 511 template <typename Func> 512 void operator()(std::string name, Func&& f) 513 { 514 nameStr = std::move(name); 515 (*this).template operator()<Func>(std::forward(f)); 516 } 517 518 private: 519 std::function<void(const Request&, 520 const std::shared_ptr<bmcweb::AsyncResp>&, 521 const std::vector<std::string>&)> 522 erasedHandler; 523 }; 524 525 template <typename... Args> 526 class TaggedRule : 527 public BaseRule, 528 public RuleParameterTraits<TaggedRule<Args...>> 529 { 530 public: 531 using self_t = TaggedRule<Args...>; 532 533 explicit TaggedRule(const std::string& ruleIn) : BaseRule(ruleIn) 534 {} 535 536 void validate() override 537 { 538 if (!handler) 539 { 540 throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") + 541 "no handler for url " + rule); 542 } 543 } 544 545 template <typename Func> 546 void operator()(Func&& f) 547 { 548 static_assert( 549 black_magic::CallHelper< 550 Func, black_magic::S<crow::Request, 551 std::shared_ptr<bmcweb::AsyncResp>&, 552 Args...>>::value, 553 "Handler type is mismatched with URL parameters"); 554 static_assert( 555 std::is_same< 556 void, 557 decltype(f(std::declval<crow::Request>(), 558 std::declval<std::shared_ptr<bmcweb::AsyncResp>&>(), 559 std::declval<Args>()...))>::value, 560 "Handler function with response argument should have void return type"); 561 562 handler = std::forward<Func>(f); 563 } 564 565 template <typename Func> 566 void operator()(std::string_view name, Func&& f) 567 { 568 nameStr = name; 569 (*this).template operator()<Func>(std::forward(f)); 570 } 571 572 void handle(const Request& req, 573 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 574 const std::vector<std::string>& params) override 575 { 576 detail::routing_handler_call_helper::Call< 577 detail::routing_handler_call_helper::CallParams<decltype(handler)>, 578 0, black_magic::S<Args...>, black_magic::S<>>()( 579 detail::routing_handler_call_helper::CallParams<decltype(handler)>{ 580 handler, params, req, asyncResp}); 581 } 582 583 private: 584 std::function<void(const crow::Request&, 585 const std::shared_ptr<bmcweb::AsyncResp>&, Args...)> 586 handler; 587 }; 588 589 class Trie 590 { 591 public: 592 struct Node 593 { 594 unsigned ruleIndex{}; 595 std::array<size_t, static_cast<size_t>(ParamType::MAX)> 596 paramChildrens{}; 597 using ChildMap = boost::container::flat_map< 598 std::string, unsigned, std::less<>, 599 std::vector<std::pair<std::string, unsigned>>>; 600 ChildMap children; 601 602 bool isSimpleNode() const 603 { 604 return ruleIndex == 0 && 605 std::all_of(std::begin(paramChildrens), 606 std::end(paramChildrens), 607 [](size_t x) { return x == 0U; }); 608 } 609 }; 610 611 Trie() : nodes(1) 612 {} 613 614 private: 615 void optimizeNode(Node* node) 616 { 617 for (size_t x : node->paramChildrens) 618 { 619 if (x == 0U) 620 { 621 continue; 622 } 623 Node* child = &nodes[x]; 624 optimizeNode(child); 625 } 626 if (node->children.empty()) 627 { 628 return; 629 } 630 bool mergeWithChild = true; 631 for (const Node::ChildMap::value_type& kv : node->children) 632 { 633 Node* child = &nodes[kv.second]; 634 if (!child->isSimpleNode()) 635 { 636 mergeWithChild = false; 637 break; 638 } 639 } 640 if (mergeWithChild) 641 { 642 Node::ChildMap merged; 643 for (const Node::ChildMap::value_type& kv : node->children) 644 { 645 Node* child = &nodes[kv.second]; 646 for (const Node::ChildMap::value_type& childKv : 647 child->children) 648 { 649 merged[kv.first + childKv.first] = childKv.second; 650 } 651 } 652 node->children = std::move(merged); 653 optimizeNode(node); 654 } 655 else 656 { 657 for (const Node::ChildMap::value_type& kv : node->children) 658 { 659 Node* child = &nodes[kv.second]; 660 optimizeNode(child); 661 } 662 } 663 } 664 665 void optimize() 666 { 667 optimizeNode(head()); 668 } 669 670 public: 671 void validate() 672 { 673 optimize(); 674 } 675 676 void findRouteIndexes(const std::string& reqUrl, 677 std::vector<unsigned>& routeIndexes, 678 const Node* node = nullptr, unsigned pos = 0) const 679 { 680 if (node == nullptr) 681 { 682 node = head(); 683 } 684 for (const Node::ChildMap::value_type& kv : node->children) 685 { 686 const std::string& fragment = kv.first; 687 const Node* child = &nodes[kv.second]; 688 if (pos >= reqUrl.size()) 689 { 690 if (child->ruleIndex != 0 && fragment != "/") 691 { 692 routeIndexes.push_back(child->ruleIndex); 693 } 694 findRouteIndexes(reqUrl, routeIndexes, child, 695 static_cast<unsigned>(pos + fragment.size())); 696 } 697 else 698 { 699 if (reqUrl.compare(pos, fragment.size(), fragment) == 0) 700 { 701 findRouteIndexes( 702 reqUrl, routeIndexes, child, 703 static_cast<unsigned>(pos + fragment.size())); 704 } 705 } 706 } 707 } 708 709 std::pair<unsigned, std::vector<std::string>> 710 find(const std::string_view reqUrl, const Node* node = nullptr, 711 size_t pos = 0, std::vector<std::string>* params = nullptr) const 712 { 713 std::vector<std::string> empty; 714 if (params == nullptr) 715 { 716 params = ∅ 717 } 718 719 unsigned found{}; 720 std::vector<std::string> matchParams; 721 722 if (node == nullptr) 723 { 724 node = head(); 725 } 726 if (pos == reqUrl.size()) 727 { 728 return {node->ruleIndex, *params}; 729 } 730 731 auto updateFound = 732 [&found, 733 &matchParams](std::pair<unsigned, std::vector<std::string>>& ret) { 734 if (ret.first != 0U && (found == 0U || found > ret.first)) 735 { 736 found = ret.first; 737 matchParams = std::move(ret.second); 738 } 739 }; 740 741 if (node->paramChildrens[static_cast<size_t>(ParamType::STRING)] != 0U) 742 { 743 size_t epos = pos; 744 for (; epos < reqUrl.size(); epos++) 745 { 746 if (reqUrl[epos] == '/') 747 { 748 break; 749 } 750 } 751 752 if (epos != pos) 753 { 754 params->emplace_back(reqUrl.substr(pos, epos - pos)); 755 std::pair<unsigned, std::vector<std::string>> ret = 756 find(reqUrl, 757 &nodes[node->paramChildrens[static_cast<size_t>( 758 ParamType::STRING)]], 759 epos, params); 760 updateFound(ret); 761 params->pop_back(); 762 } 763 } 764 765 if (node->paramChildrens[static_cast<size_t>(ParamType::PATH)] != 0U) 766 { 767 size_t epos = reqUrl.size(); 768 769 if (epos != pos) 770 { 771 params->emplace_back(reqUrl.substr(pos, epos - pos)); 772 std::pair<unsigned, std::vector<std::string>> ret = 773 find(reqUrl, 774 &nodes[node->paramChildrens[static_cast<size_t>( 775 ParamType::PATH)]], 776 epos, params); 777 updateFound(ret); 778 params->pop_back(); 779 } 780 } 781 782 for (const Node::ChildMap::value_type& kv : node->children) 783 { 784 const std::string& fragment = kv.first; 785 const Node* child = &nodes[kv.second]; 786 787 if (reqUrl.compare(pos, fragment.size(), fragment) == 0) 788 { 789 std::pair<unsigned, std::vector<std::string>> ret = 790 find(reqUrl, child, pos + fragment.size(), params); 791 updateFound(ret); 792 } 793 } 794 795 return {found, matchParams}; 796 } 797 798 void add(const std::string& url, unsigned ruleIndex) 799 { 800 size_t idx = 0; 801 802 for (unsigned i = 0; i < url.size(); i++) 803 { 804 char c = url[i]; 805 if (c == '<') 806 { 807 constexpr static std::array< 808 std::pair<ParamType, std::string_view>, 3> 809 paramTraits = {{ 810 {ParamType::STRING, "<str>"}, 811 {ParamType::STRING, "<string>"}, 812 {ParamType::PATH, "<path>"}, 813 }}; 814 815 for (const std::pair<ParamType, std::string_view>& x : 816 paramTraits) 817 { 818 if (url.compare(i, x.second.size(), x.second) == 0) 819 { 820 size_t index = static_cast<size_t>(x.first); 821 if (nodes[idx].paramChildrens[index] == 0U) 822 { 823 unsigned newNodeIdx = newNode(); 824 nodes[idx].paramChildrens[index] = newNodeIdx; 825 } 826 idx = nodes[idx].paramChildrens[index]; 827 i += static_cast<unsigned>(x.second.size()); 828 break; 829 } 830 } 831 832 i--; 833 } 834 else 835 { 836 std::string piece(&c, 1); 837 if (nodes[idx].children.count(piece) == 0U) 838 { 839 unsigned newNodeIdx = newNode(); 840 nodes[idx].children.emplace(piece, newNodeIdx); 841 } 842 idx = nodes[idx].children[piece]; 843 } 844 } 845 if (nodes[idx].ruleIndex != 0U) 846 { 847 throw std::runtime_error("handler already exists for " + url); 848 } 849 nodes[idx].ruleIndex = ruleIndex; 850 } 851 852 private: 853 void debugNodePrint(Node* n, size_t level) 854 { 855 for (size_t i = 0; i < static_cast<size_t>(ParamType::MAX); i++) 856 { 857 if (n->paramChildrens[i] != 0U) 858 { 859 BMCWEB_LOG_DEBUG << std::string( 860 2U * level, ' ') /*<< "("<<n->paramChildrens[i]<<") "*/; 861 switch (static_cast<ParamType>(i)) 862 { 863 case ParamType::STRING: 864 BMCWEB_LOG_DEBUG << "<str>"; 865 break; 866 case ParamType::PATH: 867 BMCWEB_LOG_DEBUG << "<path>"; 868 break; 869 case ParamType::MAX: 870 BMCWEB_LOG_DEBUG << "<ERROR>"; 871 break; 872 } 873 874 debugNodePrint(&nodes[n->paramChildrens[i]], level + 1); 875 } 876 } 877 for (const Node::ChildMap::value_type& kv : n->children) 878 { 879 BMCWEB_LOG_DEBUG 880 << std::string(2U * level, ' ') /*<< "(" << kv.second << ") "*/ 881 << kv.first; 882 debugNodePrint(&nodes[kv.second], level + 1); 883 } 884 } 885 886 public: 887 void debugPrint() 888 { 889 debugNodePrint(head(), 0U); 890 } 891 892 private: 893 const Node* head() const 894 { 895 return &nodes.front(); 896 } 897 898 Node* head() 899 { 900 return &nodes.front(); 901 } 902 903 unsigned newNode() 904 { 905 nodes.resize(nodes.size() + 1); 906 return static_cast<unsigned>(nodes.size() - 1); 907 } 908 909 std::vector<Node> nodes; 910 }; 911 912 class Router 913 { 914 public: 915 Router() = default; 916 917 DynamicRule& newRuleDynamic(const std::string& rule) 918 { 919 std::unique_ptr<DynamicRule> ruleObject = 920 std::make_unique<DynamicRule>(rule); 921 DynamicRule* ptr = ruleObject.get(); 922 allRules.emplace_back(std::move(ruleObject)); 923 924 return *ptr; 925 } 926 927 template <uint64_t N> 928 typename black_magic::Arguments<N>::type::template rebind<TaggedRule>& 929 newRuleTagged(const std::string& rule) 930 { 931 using RuleT = typename black_magic::Arguments<N>::type::template rebind< 932 TaggedRule>; 933 std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); 934 RuleT* ptr = ruleObject.get(); 935 allRules.emplace_back(std::move(ruleObject)); 936 937 return *ptr; 938 } 939 940 void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject) 941 { 942 if (ruleObject == nullptr) 943 { 944 return; 945 } 946 for (size_t method = 0, methodBit = 1; method <= methodNotAllowedIndex; 947 method++, methodBit <<= 1) 948 { 949 if ((ruleObject->methodsBitfield & methodBit) > 0U) 950 { 951 perMethods[method].rules.emplace_back(ruleObject); 952 perMethods[method].trie.add( 953 rule, static_cast<unsigned>( 954 perMethods[method].rules.size() - 1U)); 955 // directory case: 956 // request to `/about' url matches `/about/' rule 957 if (rule.size() > 2 && rule.back() == '/') 958 { 959 perMethods[method].trie.add( 960 rule.substr(0, rule.size() - 1), 961 static_cast<unsigned>(perMethods[method].rules.size() - 962 1)); 963 } 964 } 965 } 966 } 967 968 void validate() 969 { 970 for (std::unique_ptr<BaseRule>& rule : allRules) 971 { 972 if (rule) 973 { 974 std::unique_ptr<BaseRule> upgraded = rule->upgrade(); 975 if (upgraded) 976 { 977 rule = std::move(upgraded); 978 } 979 rule->validate(); 980 internalAddRuleObject(rule->rule, rule.get()); 981 } 982 } 983 for (PerMethod& perMethod : perMethods) 984 { 985 perMethod.trie.validate(); 986 } 987 } 988 989 struct FindRoute 990 { 991 BaseRule* rule = nullptr; 992 std::vector<std::string> params; 993 }; 994 995 struct FindRouteResponse 996 { 997 std::string allowHeader; 998 FindRoute route; 999 }; 1000 1001 FindRoute findRouteByIndex(std::string_view url, size_t index) const 1002 { 1003 FindRoute route; 1004 if (index >= perMethods.size()) 1005 { 1006 BMCWEB_LOG_CRITICAL << "Bad index???"; 1007 return route; 1008 } 1009 const PerMethod& perMethod = perMethods[index]; 1010 std::pair<unsigned, std::vector<std::string>> found = 1011 perMethod.trie.find(url); 1012 if (found.first >= perMethod.rules.size()) 1013 { 1014 throw std::runtime_error("Trie internal structure corrupted!"); 1015 } 1016 // Found a 404 route, switch that in 1017 if (found.first != 0U) 1018 { 1019 route.rule = perMethod.rules[found.first]; 1020 route.params = std::move(found.second); 1021 } 1022 return route; 1023 } 1024 1025 FindRouteResponse findRoute(Request& req) const 1026 { 1027 FindRouteResponse findRoute; 1028 1029 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 1030 if (!verb) 1031 { 1032 return findRoute; 1033 } 1034 size_t reqMethodIndex = static_cast<size_t>(*verb); 1035 // Check to see if this url exists at any verb 1036 for (size_t perMethodIndex = 0; perMethodIndex <= maxVerbIndex; 1037 perMethodIndex++) 1038 { 1039 // Make sure it's safe to deference the array at that index 1040 static_assert(maxVerbIndex < 1041 std::tuple_size_v<decltype(perMethods)>); 1042 FindRoute route = 1043 findRouteByIndex(req.url().encoded_path(), perMethodIndex); 1044 if (route.rule == nullptr) 1045 { 1046 continue; 1047 } 1048 if (!findRoute.allowHeader.empty()) 1049 { 1050 findRoute.allowHeader += ", "; 1051 } 1052 HttpVerb thisVerb = static_cast<HttpVerb>(perMethodIndex); 1053 findRoute.allowHeader += httpVerbToString(thisVerb); 1054 if (perMethodIndex == reqMethodIndex) 1055 { 1056 findRoute.route = route; 1057 } 1058 } 1059 return findRoute; 1060 } 1061 1062 static bool isUserPrivileged( 1063 Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1064 BaseRule& rule, const dbus::utility::DBusPropertiesMap& userInfoMap) 1065 { 1066 std::string userRole{}; 1067 const std::string* userRolePtr = nullptr; 1068 const bool* remoteUser = nullptr; 1069 const bool* passwordExpired = nullptr; 1070 1071 const bool success = sdbusplus::unpackPropertiesNoThrow( 1072 redfish::dbus_utils::UnpackErrorPrinter(), userInfoMap, 1073 "UserPrivilege", userRolePtr, "RemoteUser", remoteUser, 1074 "UserPasswordExpired", passwordExpired); 1075 1076 if (!success) 1077 { 1078 asyncResp->res.result( 1079 boost::beast::http::status::internal_server_error); 1080 return false; 1081 } 1082 1083 if (userRolePtr != nullptr) 1084 { 1085 userRole = *userRolePtr; 1086 BMCWEB_LOG_DEBUG << "userName = " << req.session->username 1087 << " userRole = " << *userRolePtr; 1088 } 1089 1090 if (remoteUser == nullptr) 1091 { 1092 BMCWEB_LOG_ERROR << "RemoteUser property missing or wrong type"; 1093 asyncResp->res.result( 1094 boost::beast::http::status::internal_server_error); 1095 return false; 1096 } 1097 bool expired = false; 1098 if (passwordExpired == nullptr) 1099 { 1100 if (!*remoteUser) 1101 { 1102 BMCWEB_LOG_ERROR 1103 << "UserPasswordExpired property is expected for" 1104 " local user but is missing or wrong type"; 1105 asyncResp->res.result( 1106 boost::beast::http::status::internal_server_error); 1107 return false; 1108 } 1109 } 1110 else 1111 { 1112 expired = *passwordExpired; 1113 } 1114 1115 // Get the user's privileges from the role 1116 redfish::Privileges userPrivileges = 1117 redfish::getUserPrivileges(userRole); 1118 1119 // Set isConfigureSelfOnly based on D-Bus results. This 1120 // ignores the results from both pamAuthenticateUser and the 1121 // value from any previous use of this session. 1122 req.session->isConfigureSelfOnly = expired; 1123 1124 // Modify privileges if isConfigureSelfOnly. 1125 if (req.session->isConfigureSelfOnly) 1126 { 1127 // Remove all privileges except ConfigureSelf 1128 userPrivileges = userPrivileges.intersection( 1129 redfish::Privileges{"ConfigureSelf"}); 1130 BMCWEB_LOG_DEBUG << "Operation limited to ConfigureSelf"; 1131 } 1132 1133 if (!rule.checkPrivileges(userPrivileges)) 1134 { 1135 asyncResp->res.result(boost::beast::http::status::forbidden); 1136 if (req.session->isConfigureSelfOnly) 1137 { 1138 redfish::messages::passwordChangeRequired( 1139 asyncResp->res, crow::utility::urlFromPieces( 1140 "redfish", "v1", "AccountService", 1141 "Accounts", req.session->username)); 1142 } 1143 return false; 1144 } 1145 1146 req.userRole = userRole; 1147 1148 return true; 1149 } 1150 1151 template <typename CallbackFn> 1152 void afterGetUserInfo(Request& req, 1153 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1154 BaseRule& rule, CallbackFn&& callback, 1155 const boost::system::error_code& ec, 1156 const dbus::utility::DBusPropertiesMap& userInfoMap) 1157 { 1158 if (ec) 1159 { 1160 BMCWEB_LOG_ERROR << "GetUserInfo failed..."; 1161 asyncResp->res.result( 1162 boost::beast::http::status::internal_server_error); 1163 return; 1164 } 1165 1166 if (!Router::isUserPrivileged(req, asyncResp, rule, userInfoMap)) 1167 { 1168 // User is not privileged 1169 BMCWEB_LOG_ERROR << "Insufficient Privilege"; 1170 asyncResp->res.result(boost::beast::http::status::forbidden); 1171 return; 1172 } 1173 callback(req); 1174 } 1175 1176 template <typename CallbackFn> 1177 void validatePrivilege(Request& req, 1178 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1179 BaseRule& rule, CallbackFn&& callback) 1180 { 1181 if (req.session == nullptr) 1182 { 1183 return; 1184 } 1185 std::string username = req.session->username; 1186 crow::connections::systemBus->async_method_call( 1187 [this, &req, asyncResp, &rule, 1188 callback(std::forward<CallbackFn>(callback))]( 1189 const boost::system::error_code& ec, 1190 const dbus::utility::DBusPropertiesMap& userInfoMap) mutable { 1191 afterGetUserInfo(req, asyncResp, rule, 1192 std::forward<CallbackFn>(callback), ec, 1193 userInfoMap); 1194 }, 1195 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1196 "xyz.openbmc_project.User.Manager", "GetUserInfo", username); 1197 } 1198 1199 template <typename Adaptor> 1200 void handleUpgrade(Request& req, 1201 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1202 Adaptor&& adaptor) 1203 { 1204 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 1205 if (!verb || static_cast<size_t>(*verb) >= perMethods.size()) 1206 { 1207 asyncResp->res.result(boost::beast::http::status::not_found); 1208 return; 1209 } 1210 PerMethod& perMethod = perMethods[static_cast<size_t>(*verb)]; 1211 Trie& trie = perMethod.trie; 1212 std::vector<BaseRule*>& rules = perMethod.rules; 1213 1214 const std::pair<unsigned, std::vector<std::string>>& found = 1215 trie.find(req.url().encoded_path()); 1216 unsigned ruleIndex = found.first; 1217 if (ruleIndex == 0U) 1218 { 1219 BMCWEB_LOG_DEBUG << "Cannot match rules " 1220 << req.url().encoded_path(); 1221 asyncResp->res.result(boost::beast::http::status::not_found); 1222 return; 1223 } 1224 1225 if (ruleIndex >= rules.size()) 1226 { 1227 throw std::runtime_error("Trie internal structure corrupted!"); 1228 } 1229 1230 BaseRule& rule = *rules[ruleIndex]; 1231 size_t methods = rule.getMethods(); 1232 if ((methods & (1U << static_cast<size_t>(*verb))) == 0) 1233 { 1234 BMCWEB_LOG_DEBUG 1235 << "Rule found but method mismatch: " 1236 << req.url().encoded_path() << " with " << req.methodString() 1237 << "(" << static_cast<uint32_t>(*verb) << ") / " << methods; 1238 asyncResp->res.result(boost::beast::http::status::not_found); 1239 return; 1240 } 1241 1242 BMCWEB_LOG_DEBUG << "Matched rule (upgrade) '" << rule.rule << "' " 1243 << static_cast<uint32_t>(*verb) << " / " << methods; 1244 1245 // TODO(ed) This should be able to use std::bind_front, but it doesn't 1246 // appear to work with the std::move on adaptor. 1247 validatePrivilege( 1248 req, asyncResp, rule, 1249 [&rule, asyncResp, adaptor(std::forward<Adaptor>(adaptor))]( 1250 Request& thisReq) mutable { 1251 rule.handleUpgrade(thisReq, asyncResp, std::move(adaptor)); 1252 }); 1253 } 1254 1255 void handle(Request& req, 1256 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1257 { 1258 std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); 1259 if (!verb || static_cast<size_t>(*verb) >= perMethods.size()) 1260 { 1261 asyncResp->res.result(boost::beast::http::status::not_found); 1262 return; 1263 } 1264 1265 FindRouteResponse foundRoute = findRoute(req); 1266 1267 if (foundRoute.route.rule == nullptr) 1268 { 1269 // Couldn't find a normal route with any verb, try looking for a 404 1270 // route 1271 if (foundRoute.allowHeader.empty()) 1272 { 1273 foundRoute.route = 1274 findRouteByIndex(req.url().encoded_path(), notFoundIndex); 1275 } 1276 else 1277 { 1278 // See if we have a method not allowed (405) handler 1279 foundRoute.route = findRouteByIndex(req.url().encoded_path(), 1280 methodNotAllowedIndex); 1281 } 1282 } 1283 1284 // Fill in the allow header if it's valid 1285 if (!foundRoute.allowHeader.empty()) 1286 { 1287 1288 asyncResp->res.addHeader(boost::beast::http::field::allow, 1289 foundRoute.allowHeader); 1290 } 1291 1292 // If we couldn't find a real route or a 404 route, return a generic 1293 // response 1294 if (foundRoute.route.rule == nullptr) 1295 { 1296 if (foundRoute.allowHeader.empty()) 1297 { 1298 asyncResp->res.result(boost::beast::http::status::not_found); 1299 } 1300 else 1301 { 1302 asyncResp->res.result( 1303 boost::beast::http::status::method_not_allowed); 1304 } 1305 return; 1306 } 1307 1308 BaseRule& rule = *foundRoute.route.rule; 1309 std::vector<std::string> params = std::move(foundRoute.route.params); 1310 1311 BMCWEB_LOG_DEBUG << "Matched rule '" << rule.rule << "' " 1312 << static_cast<uint32_t>(*verb) << " / " 1313 << rule.getMethods(); 1314 1315 if (req.session == nullptr) 1316 { 1317 rule.handle(req, asyncResp, params); 1318 return; 1319 } 1320 validatePrivilege(req, asyncResp, rule, 1321 [&rule, asyncResp, params](Request& thisReq) mutable { 1322 rule.handle(thisReq, asyncResp, params); 1323 }); 1324 } 1325 1326 void debugPrint() 1327 { 1328 for (size_t i = 0; i < perMethods.size(); i++) 1329 { 1330 BMCWEB_LOG_DEBUG << boost::beast::http::to_string( 1331 static_cast<boost::beast::http::verb>(i)); 1332 perMethods[i].trie.debugPrint(); 1333 } 1334 } 1335 1336 std::vector<const std::string*> getRoutes(const std::string& parent) 1337 { 1338 std::vector<const std::string*> ret; 1339 1340 for (const PerMethod& pm : perMethods) 1341 { 1342 std::vector<unsigned> x; 1343 pm.trie.findRouteIndexes(parent, x); 1344 for (unsigned index : x) 1345 { 1346 ret.push_back(&pm.rules[index]->rule); 1347 } 1348 } 1349 return ret; 1350 } 1351 1352 private: 1353 struct PerMethod 1354 { 1355 std::vector<BaseRule*> rules; 1356 Trie trie; 1357 // rule index 0 has special meaning; preallocate it to avoid 1358 // duplication. 1359 PerMethod() : rules(1) 1360 {} 1361 }; 1362 1363 std::array<PerMethod, methodNotAllowedIndex + 1> perMethods; 1364 std::vector<std::unique_ptr<BaseRule>> allRules; 1365 }; 1366 } // namespace crow 1367