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