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