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