1 #pragma once 2 #include "bmcweb_config.h" 3 4 #include "async_resp.hpp" 5 #include "authentication.hpp" 6 #include "complete_response_fields.hpp" 7 #include "http2_connection.hpp" 8 #include "http_body.hpp" 9 #include "http_response.hpp" 10 #include "http_utility.hpp" 11 #include "logging.hpp" 12 #include "mutual_tls.hpp" 13 #include "ssl_key_handler.hpp" 14 #include "str_utility.hpp" 15 #include "utility.hpp" 16 17 #include <boost/asio/io_context.hpp> 18 #include <boost/asio/ip/tcp.hpp> 19 #include <boost/asio/ssl/stream.hpp> 20 #include <boost/asio/steady_timer.hpp> 21 #include <boost/beast/_experimental/test/stream.hpp> 22 #include <boost/beast/core/buffers_generator.hpp> 23 #include <boost/beast/core/flat_static_buffer.hpp> 24 #include <boost/beast/http/error.hpp> 25 #include <boost/beast/http/message_generator.hpp> 26 #include <boost/beast/http/parser.hpp> 27 #include <boost/beast/http/read.hpp> 28 #include <boost/beast/http/write.hpp> 29 #include <boost/beast/websocket.hpp> 30 31 #include <atomic> 32 #include <chrono> 33 #include <vector> 34 35 namespace crow 36 { 37 38 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 39 static int connectionCount = 0; 40 41 // request body limit size set by the bmcwebHttpReqBodyLimitMb option 42 constexpr uint64_t httpReqBodyLimit = 1024UL * 1024UL * 43 bmcwebHttpReqBodyLimitMb; 44 45 constexpr uint64_t loggedOutPostBodyLimit = 4096U; 46 47 constexpr uint32_t httpHeaderLimit = 8192U; 48 49 template <typename> 50 struct IsTls : std::false_type 51 {}; 52 53 template <typename T> 54 struct IsTls<boost::asio::ssl::stream<T>> : std::true_type 55 {}; 56 57 template <typename Adaptor, typename Handler> 58 class Connection : 59 public std::enable_shared_from_this<Connection<Adaptor, Handler>> 60 { 61 using self_type = Connection<Adaptor, Handler>; 62 63 public: 64 Connection(Handler* handlerIn, boost::asio::steady_timer&& timerIn, 65 std::function<std::string()>& getCachedDateStrF, 66 Adaptor adaptorIn) : 67 adaptor(std::move(adaptorIn)), 68 handler(handlerIn), timer(std::move(timerIn)), 69 getCachedDateStr(getCachedDateStrF) 70 { 71 initParser(); 72 73 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 74 prepareMutualTls(); 75 #endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 76 77 connectionCount++; 78 79 BMCWEB_LOG_DEBUG("{} Connection created, total {}", logPtr(this), 80 connectionCount); 81 } 82 83 ~Connection() 84 { 85 res.releaseCompleteRequestHandler(); 86 cancelDeadlineTimer(); 87 88 connectionCount--; 89 BMCWEB_LOG_DEBUG("{} Connection closed, total {}", logPtr(this), 90 connectionCount); 91 } 92 93 Connection(const Connection&) = delete; 94 Connection(Connection&&) = delete; 95 Connection& operator=(const Connection&) = delete; 96 Connection& operator=(Connection&&) = delete; 97 98 bool tlsVerifyCallback(bool preverified, 99 boost::asio::ssl::verify_context& ctx) 100 { 101 // We always return true to allow full auth flow for resources that 102 // don't require auth 103 if (preverified) 104 { 105 mtlsSession = verifyMtlsUser(ip, ctx); 106 if (mtlsSession) 107 { 108 BMCWEB_LOG_DEBUG("{} Generating TLS session: {}", logPtr(this), 109 mtlsSession->uniqueId); 110 } 111 } 112 return true; 113 } 114 115 void prepareMutualTls() 116 { 117 if constexpr (IsTls<Adaptor>::value) 118 { 119 std::error_code error; 120 std::filesystem::path caPath(ensuressl::trustStorePath); 121 auto caAvailable = !std::filesystem::is_empty(caPath, error); 122 caAvailable = caAvailable && !error; 123 if (caAvailable && persistent_data::SessionStore::getInstance() 124 .getAuthMethodsConfig() 125 .tls) 126 { 127 adaptor.set_verify_mode(boost::asio::ssl::verify_peer); 128 std::string id = "bmcweb"; 129 130 const char* cStr = id.c_str(); 131 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 132 const auto* idC = reinterpret_cast<const unsigned char*>(cStr); 133 int ret = SSL_set_session_id_context( 134 adaptor.native_handle(), idC, 135 static_cast<unsigned int>(id.length())); 136 if (ret == 0) 137 { 138 BMCWEB_LOG_ERROR("{} failed to set SSL id", logPtr(this)); 139 } 140 } 141 142 adaptor.set_verify_callback( 143 std::bind_front(&self_type::tlsVerifyCallback, this)); 144 } 145 } 146 147 Adaptor& socket() 148 { 149 return adaptor; 150 } 151 152 void start() 153 { 154 BMCWEB_LOG_DEBUG("{} Connection started, total {}", logPtr(this), 155 connectionCount); 156 if (connectionCount >= 200) 157 { 158 BMCWEB_LOG_CRITICAL("{} Max connection count exceeded.", 159 logPtr(this)); 160 return; 161 } 162 163 startDeadline(); 164 165 readClientIp(); 166 167 // TODO(ed) Abstract this to a more clever class with the idea of an 168 // asynchronous "start" 169 if constexpr (IsTls<Adaptor>::value) 170 { 171 adaptor.async_handshake(boost::asio::ssl::stream_base::server, 172 [this, self(shared_from_this())]( 173 const boost::system::error_code& ec) { 174 if (ec) 175 { 176 return; 177 } 178 afterSslHandshake(); 179 }); 180 } 181 else 182 { 183 doReadHeaders(); 184 } 185 } 186 187 void afterSslHandshake() 188 { 189 // If http2 is enabled, negotiate the protocol 190 if constexpr (bmcwebEnableHTTP2) 191 { 192 const unsigned char* alpn = nullptr; 193 unsigned int alpnlen = 0; 194 SSL_get0_alpn_selected(adaptor.native_handle(), &alpn, &alpnlen); 195 if (alpn != nullptr) 196 { 197 std::string_view selectedProtocol( 198 std::bit_cast<const char*>(alpn), alpnlen); 199 BMCWEB_LOG_DEBUG("ALPN selected protocol \"{}\" len: {}", 200 selectedProtocol, alpnlen); 201 if (selectedProtocol == "h2") 202 { 203 auto http2 = 204 std::make_shared<HTTP2Connection<Adaptor, Handler>>( 205 std::move(adaptor), handler, getCachedDateStr); 206 http2->start(); 207 return; 208 } 209 } 210 } 211 212 doReadHeaders(); 213 } 214 215 void initParser() 216 { 217 boost::beast::http::request_parser<bmcweb::HttpBody>& instance = 218 parser.emplace(std::piecewise_construct, std::make_tuple()); 219 220 // reset header limit for newly created parser 221 instance.header_limit(httpHeaderLimit); 222 223 // Initially set no body limit. We don't yet know if the user is 224 // authenticated. 225 instance.body_limit(boost::none); 226 } 227 228 void handle() 229 { 230 std::error_code reqEc; 231 if (!parser) 232 { 233 return; 234 } 235 req = crow::Request(parser->release(), reqEc); 236 if (reqEc) 237 { 238 BMCWEB_LOG_DEBUG("Request failed to construct{}", reqEc.message()); 239 res.result(boost::beast::http::status::bad_request); 240 completeRequest(res); 241 return; 242 } 243 req.session = userSession; 244 245 // Fetch the client IP address 246 req.ipAddress = ip; 247 248 // Check for HTTP version 1.1. 249 if (req.version() == 11) 250 { 251 if (req.getHeaderValue(boost::beast::http::field::host).empty()) 252 { 253 res.result(boost::beast::http::status::bad_request); 254 completeRequest(res); 255 return; 256 } 257 } 258 259 BMCWEB_LOG_INFO("Request: {} HTTP/{}.{} {} {} {}", logPtr(this), 260 req.version() / 10, req.version() % 10, 261 req.methodString(), req.target(), 262 req.ipAddress.to_string()); 263 264 req.ioService = static_cast<decltype(req.ioService)>( 265 &adaptor.get_executor().context()); 266 267 if (res.completed) 268 { 269 completeRequest(res); 270 return; 271 } 272 keepAlive = req.keepAlive(); 273 if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>) 274 { 275 #ifndef BMCWEB_INSECURE_DISABLE_AUTHX 276 if (!crow::authentication::isOnAllowlist(req.url().path(), 277 req.method()) && 278 req.session == nullptr) 279 { 280 BMCWEB_LOG_WARNING("Authentication failed"); 281 forward_unauthorized::sendUnauthorized( 282 req.url().encoded_path(), 283 req.getHeaderValue("X-Requested-With"), 284 req.getHeaderValue("Accept"), res); 285 completeRequest(res); 286 return; 287 } 288 #endif // BMCWEB_INSECURE_DISABLE_AUTHX 289 } 290 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(); 291 BMCWEB_LOG_DEBUG("Setting completion handler"); 292 asyncResp->res.setCompleteRequestHandler( 293 [self(shared_from_this())](crow::Response& thisRes) { 294 self->completeRequest(thisRes); 295 }); 296 bool isSse = 297 isContentTypeAllowed(req.getHeaderValue("Accept"), 298 http_helpers::ContentType::EventStream, false); 299 std::string_view upgradeType( 300 req.getHeaderValue(boost::beast::http::field::upgrade)); 301 if ((req.isUpgrade() && 302 bmcweb::asciiIEquals(upgradeType, "websocket")) || 303 isSse) 304 { 305 asyncResp->res.setCompleteRequestHandler( 306 [self(shared_from_this())](crow::Response& thisRes) { 307 if (thisRes.result() != boost::beast::http::status::ok) 308 { 309 // When any error occurs before handle upgradation, 310 // the result in response will be set to respective 311 // error. By default the Result will be OK (200), 312 // which implies successful handle upgrade. Response 313 // needs to be sent over this connection only on 314 // failure. 315 self->completeRequest(thisRes); 316 return; 317 } 318 }); 319 handler->handleUpgrade(req, asyncResp, std::move(adaptor)); 320 return; 321 } 322 std::string_view expected = 323 req.getHeaderValue(boost::beast::http::field::if_none_match); 324 if (!expected.empty()) 325 { 326 res.setExpectedHash(expected); 327 } 328 handler->handle(req, asyncResp); 329 } 330 331 void hardClose() 332 { 333 BMCWEB_LOG_DEBUG("{} Closing socket", logPtr(this)); 334 boost::beast::get_lowest_layer(adaptor).close(); 335 } 336 337 void tlsShutdownComplete(const std::shared_ptr<self_type>& self, 338 const boost::system::error_code& ec) 339 { 340 if (ec) 341 { 342 BMCWEB_LOG_WARNING("{} Failed to shut down TLS cleanly {}", 343 logPtr(self.get()), ec); 344 } 345 self->hardClose(); 346 } 347 348 void gracefulClose() 349 { 350 BMCWEB_LOG_DEBUG("{} Socket close requested", logPtr(this)); 351 if (mtlsSession != nullptr) 352 { 353 BMCWEB_LOG_DEBUG("{} Removing TLS session: {}", logPtr(this), 354 mtlsSession->uniqueId); 355 persistent_data::SessionStore::getInstance().removeSession( 356 mtlsSession); 357 } 358 if constexpr (IsTls<Adaptor>::value) 359 { 360 adaptor.async_shutdown(std::bind_front( 361 &self_type::tlsShutdownComplete, this, shared_from_this())); 362 } 363 else 364 { 365 hardClose(); 366 } 367 } 368 369 void completeRequest(crow::Response& thisRes) 370 { 371 res = std::move(thisRes); 372 res.keepAlive(keepAlive); 373 374 completeResponseFields(req, res); 375 res.addHeader(boost::beast::http::field::date, getCachedDateStr()); 376 377 doWrite(); 378 379 // delete lambda with self shared_ptr 380 // to enable connection destruction 381 res.setCompleteRequestHandler(nullptr); 382 } 383 384 void readClientIp() 385 { 386 boost::system::error_code ec; 387 388 if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>) 389 { 390 boost::asio::ip::tcp::endpoint endpoint = 391 boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec); 392 393 if (ec) 394 { 395 // If remote endpoint fails keep going. "ClientOriginIPAddress" 396 // will be empty. 397 BMCWEB_LOG_ERROR( 398 "Failed to get the client's IP Address. ec : {}", ec); 399 return; 400 } 401 ip = endpoint.address(); 402 } 403 } 404 405 private: 406 uint64_t getContentLengthLimit() 407 { 408 #ifndef BMCWEB_INSECURE_DISABLE_AUTHX 409 if (userSession == nullptr) 410 { 411 return loggedOutPostBodyLimit; 412 } 413 #endif 414 415 return httpReqBodyLimit; 416 } 417 418 // Returns true if content length was within limits 419 // Returns false if content length error has been returned 420 bool handleContentLengthError() 421 { 422 if (!parser) 423 { 424 BMCWEB_LOG_CRITICAL("Paser was null"); 425 return false; 426 } 427 const boost::optional<uint64_t> contentLength = 428 parser->content_length(); 429 if (!contentLength) 430 { 431 BMCWEB_LOG_DEBUG("{} No content length available", logPtr(this)); 432 return true; 433 } 434 435 uint64_t maxAllowedContentLength = getContentLengthLimit(); 436 437 if (*contentLength > maxAllowedContentLength) 438 { 439 // If the users content limit is between the logged in 440 // and logged out limits They probably just didn't log 441 // in 442 if (*contentLength > loggedOutPostBodyLimit && 443 *contentLength < httpReqBodyLimit) 444 { 445 BMCWEB_LOG_DEBUG( 446 "{} Content length {} valid, but greater than logged out" 447 " limit of {}. Setting unauthorized", 448 logPtr(this), *contentLength, loggedOutPostBodyLimit); 449 res.result(boost::beast::http::status::unauthorized); 450 } 451 else 452 { 453 // Otherwise they're over both limits, so inform 454 // them 455 BMCWEB_LOG_DEBUG( 456 "{} Content length {} was greater than global limit {}." 457 " Setting payload too large", 458 logPtr(this), *contentLength, httpReqBodyLimit); 459 res.result(boost::beast::http::status::payload_too_large); 460 } 461 462 keepAlive = false; 463 doWrite(); 464 return false; 465 } 466 467 return true; 468 } 469 470 void doReadHeaders() 471 { 472 BMCWEB_LOG_DEBUG("{} doReadHeaders", logPtr(this)); 473 if (!parser) 474 { 475 BMCWEB_LOG_CRITICAL("Parser was not initialized."); 476 return; 477 } 478 // Clean up any previous Connection. 479 boost::beast::http::async_read_header( 480 adaptor, buffer, *parser, 481 [this, 482 self(shared_from_this())](const boost::system::error_code& ec, 483 std::size_t bytesTransferred) { 484 BMCWEB_LOG_DEBUG("{} async_read_header {} Bytes", logPtr(this), 485 bytesTransferred); 486 487 if (ec) 488 { 489 cancelDeadlineTimer(); 490 491 if (ec == boost::beast::http::error::header_limit) 492 { 493 BMCWEB_LOG_ERROR("{} Header field too large, closing", 494 logPtr(this), ec.message()); 495 496 res.result(boost::beast::http::status:: 497 request_header_fields_too_large); 498 keepAlive = false; 499 doWrite(); 500 return; 501 } 502 if (ec == boost::beast::http::error::end_of_stream) 503 { 504 BMCWEB_LOG_WARNING("{} End of stream, closing {}", 505 logPtr(this), ec); 506 hardClose(); 507 return; 508 } 509 510 BMCWEB_LOG_DEBUG("{} Closing socket due to read error {}", 511 logPtr(this), ec.message()); 512 gracefulClose(); 513 514 return; 515 } 516 517 std::string_view expect = 518 req.getHeaderValue(boost::beast::http::field::expect); 519 if (bmcweb::asciiIEquals(expect, "100-continue")) 520 { 521 res.result(boost::beast::http::status::continue_); 522 doWrite(); 523 return; 524 } 525 526 if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>) 527 { 528 #ifndef BMCWEB_INSECURE_DISABLE_AUTHX 529 boost::beast::http::verb method = parser->get().method(); 530 userSession = crow::authentication::authenticate( 531 ip, res, method, parser->get().base(), mtlsSession); 532 #endif // BMCWEB_INSECURE_DISABLE_AUTHX 533 } 534 535 if (!handleContentLengthError()) 536 { 537 return; 538 } 539 540 parser->body_limit(getContentLengthLimit()); 541 542 if (parser->is_done()) 543 { 544 handle(); 545 return; 546 } 547 548 doRead(); 549 }); 550 } 551 552 void doRead() 553 { 554 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this)); 555 if (!parser) 556 { 557 return; 558 } 559 startDeadline(); 560 boost::beast::http::async_read_some( 561 adaptor, buffer, *parser, 562 [this, 563 self(shared_from_this())](const boost::system::error_code& ec, 564 std::size_t bytesTransferred) { 565 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this), 566 bytesTransferred); 567 568 if (ec) 569 { 570 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this), 571 ec.message()); 572 if (ec == boost::beast::http::error::body_limit) 573 { 574 if (handleContentLengthError()) 575 { 576 BMCWEB_LOG_CRITICAL("Body length limit reached, " 577 "but no content-length " 578 "available? Should never happen"); 579 res.result( 580 boost::beast::http::status::internal_server_error); 581 keepAlive = false; 582 doWrite(); 583 } 584 return; 585 } 586 587 gracefulClose(); 588 return; 589 } 590 591 // If the user is logged in, allow them to send files incrementally 592 // one piece at a time. If authentication is disabled then there is 593 // no user session hence always allow to send one piece at a time. 594 if (userSession != nullptr) 595 { 596 cancelDeadlineTimer(); 597 } 598 if (!parser->is_done()) 599 { 600 doRead(); 601 return; 602 } 603 604 cancelDeadlineTimer(); 605 handle(); 606 }); 607 } 608 609 void afterDoWrite(const std::shared_ptr<self_type>& /*self*/, 610 const boost::system::error_code& ec, 611 std::size_t bytesTransferred) 612 { 613 BMCWEB_LOG_DEBUG("{} async_write wrote {} bytes, ec=", logPtr(this), 614 bytesTransferred, ec); 615 616 cancelDeadlineTimer(); 617 618 if (ec) 619 { 620 BMCWEB_LOG_DEBUG("{} from write(2)", logPtr(this)); 621 return; 622 } 623 624 if (res.result() == boost::beast::http::status::continue_) 625 { 626 // Reset the result to ok 627 res.result(boost::beast::http::status::ok); 628 doRead(); 629 return; 630 } 631 632 if (!keepAlive) 633 { 634 BMCWEB_LOG_DEBUG("{} keepalive not set. Closing socket", 635 logPtr(this)); 636 637 gracefulClose(); 638 return; 639 } 640 641 BMCWEB_LOG_DEBUG("{} Clearing response", logPtr(this)); 642 res.clear(); 643 initParser(); 644 645 userSession = nullptr; 646 647 // Destroy the Request via the std::optional 648 req.clear(); 649 doReadHeaders(); 650 } 651 652 void doWrite() 653 { 654 BMCWEB_LOG_DEBUG("{} doWrite", logPtr(this)); 655 res.preparePayload(); 656 657 startDeadline(); 658 boost::beast::async_write( 659 adaptor, 660 boost::beast::http::message_generator(std::move(res.response)), 661 std::bind_front(&self_type::afterDoWrite, this, 662 shared_from_this())); 663 } 664 665 void cancelDeadlineTimer() 666 { 667 timer.cancel(); 668 } 669 670 void startDeadline() 671 { 672 // Timer is already started so no further action is required. 673 if (timerStarted) 674 { 675 return; 676 } 677 678 std::chrono::seconds timeout(15); 679 680 std::weak_ptr<Connection<Adaptor, Handler>> weakSelf = weak_from_this(); 681 timer.expires_after(timeout); 682 timer.async_wait([weakSelf](const boost::system::error_code& ec) { 683 // Note, we are ignoring other types of errors here; If the timer 684 // failed for any reason, we should still close the connection 685 std::shared_ptr<Connection<Adaptor, Handler>> self = 686 weakSelf.lock(); 687 if (!self) 688 { 689 if (ec == boost::asio::error::operation_aborted) 690 { 691 BMCWEB_LOG_DEBUG( 692 "{} Timer canceled on connection being destroyed", 693 logPtr(self.get())); 694 return; 695 } 696 BMCWEB_LOG_CRITICAL("{} Failed to capture connection", 697 logPtr(self.get())); 698 return; 699 } 700 701 self->timerStarted = false; 702 703 if (ec) 704 { 705 if (ec == boost::asio::error::operation_aborted) 706 { 707 BMCWEB_LOG_DEBUG("{} Timer canceled", logPtr(self.get())); 708 return; 709 } 710 BMCWEB_LOG_CRITICAL("{} Timer failed {}", logPtr(self.get()), 711 ec); 712 } 713 714 BMCWEB_LOG_WARNING("{} Connection timed out, hard closing", 715 logPtr(self.get())); 716 717 self->hardClose(); 718 }); 719 720 timerStarted = true; 721 BMCWEB_LOG_DEBUG("{} timer started", logPtr(this)); 722 } 723 724 Adaptor adaptor; 725 Handler* handler; 726 727 boost::asio::ip::address ip; 728 729 // Making this a std::optional allows it to be efficiently destroyed and 730 // re-created on Connection reset 731 std::optional<boost::beast::http::request_parser<bmcweb::HttpBody>> parser; 732 733 boost::beast::flat_static_buffer<8192> buffer; 734 735 crow::Request req; 736 crow::Response res; 737 738 std::shared_ptr<persistent_data::UserSession> userSession; 739 std::shared_ptr<persistent_data::UserSession> mtlsSession; 740 741 boost::asio::steady_timer timer; 742 743 bool keepAlive = true; 744 745 bool timerStarted = false; 746 747 std::function<std::string()>& getCachedDateStr; 748 749 using std::enable_shared_from_this< 750 Connection<Adaptor, Handler>>::shared_from_this; 751 752 using std::enable_shared_from_this< 753 Connection<Adaptor, Handler>>::weak_from_this; 754 }; 755 } // namespace crow 756