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