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