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