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