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