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/buffers_generator.hpp> 21 #include <boost/beast/core/flat_static_buffer.hpp> 22 #include <boost/beast/http/error.hpp> 23 #include <boost/beast/http/parser.hpp> 24 #include <boost/beast/http/read.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 afterDoWrite(const std::shared_ptr<self_type>& /*self*/, 536 const boost::system::error_code& ec, 537 std::size_t bytesTransferred) 538 { 539 BMCWEB_LOG_DEBUG("{} async_write {} bytes", logPtr(this), 540 bytesTransferred); 541 542 cancelDeadlineTimer(); 543 544 if (ec) 545 { 546 BMCWEB_LOG_DEBUG("{} from write(2)", logPtr(this)); 547 return; 548 } 549 if (!keepAlive) 550 { 551 close(); 552 BMCWEB_LOG_DEBUG("{} from write(1)", logPtr(this)); 553 return; 554 } 555 556 BMCWEB_LOG_DEBUG("{} Clearing response", logPtr(this)); 557 res.clear(); 558 parser.emplace(std::piecewise_construct, std::make_tuple()); 559 parser->body_limit(httpReqBodyLimit); // reset body limit for 560 // newly created parser 561 buffer.consume(buffer.size()); 562 563 userSession = nullptr; 564 565 // Destroy the Request via the std::optional 566 req.reset(); 567 doReadHeaders(); 568 } 569 570 void doWrite(crow::Response& thisRes) 571 { 572 BMCWEB_LOG_DEBUG("{} doWrite", logPtr(this)); 573 thisRes.preparePayload(); 574 575 startDeadline(); 576 boost::beast::async_write(adaptor, thisRes.generator(), 577 std::bind_front(&self_type::afterDoWrite, 578 this, shared_from_this())); 579 } 580 581 void cancelDeadlineTimer() 582 { 583 timer.cancel(); 584 } 585 586 void startDeadline() 587 { 588 // Timer is already started so no further action is required. 589 if (timerStarted) 590 { 591 return; 592 } 593 594 std::chrono::seconds timeout(15); 595 596 std::weak_ptr<Connection<Adaptor, Handler>> weakSelf = weak_from_this(); 597 timer.expires_after(timeout); 598 timer.async_wait([weakSelf](const boost::system::error_code& ec) { 599 // Note, we are ignoring other types of errors here; If the timer 600 // failed for any reason, we should still close the connection 601 std::shared_ptr<Connection<Adaptor, Handler>> self = 602 weakSelf.lock(); 603 if (!self) 604 { 605 BMCWEB_LOG_CRITICAL("{} Failed to capture connection", 606 logPtr(self.get())); 607 return; 608 } 609 610 self->timerStarted = false; 611 612 if (ec == boost::asio::error::operation_aborted) 613 { 614 // Canceled wait means the path succeeeded. 615 return; 616 } 617 if (ec) 618 { 619 BMCWEB_LOG_CRITICAL("{} timer failed {}", logPtr(self.get()), 620 ec); 621 } 622 623 BMCWEB_LOG_WARNING("{}Connection timed out, closing", 624 logPtr(self.get())); 625 626 self->close(); 627 }); 628 629 timerStarted = true; 630 BMCWEB_LOG_DEBUG("{} timer started", logPtr(this)); 631 } 632 633 Adaptor adaptor; 634 Handler* handler; 635 // Making this a std::optional allows it to be efficiently destroyed and 636 // re-created on Connection reset 637 std::optional< 638 boost::beast::http::request_parser<boost::beast::http::string_body>> 639 parser; 640 641 boost::beast::flat_static_buffer<8192> buffer; 642 643 std::optional<crow::Request> req; 644 crow::Response res; 645 646 std::shared_ptr<persistent_data::UserSession> userSession; 647 std::shared_ptr<persistent_data::UserSession> mtlsSession; 648 649 boost::asio::steady_timer timer; 650 651 bool keepAlive = true; 652 653 bool timerStarted = false; 654 655 std::function<std::string()>& getCachedDateStr; 656 657 using std::enable_shared_from_this< 658 Connection<Adaptor, Handler>>::shared_from_this; 659 660 using std::enable_shared_from_this< 661 Connection<Adaptor, Handler>>::weak_from_this; 662 }; 663 } // namespace crow 664