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