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