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