1 #pragma once 2 #include "config.h" 3 4 #include "authorization.hpp" 5 #include "http_response.hpp" 6 #include "http_utility.hpp" 7 #include "logging.hpp" 8 #include "timer_queue.hpp" 9 #include "utility.hpp" 10 11 #include <boost/algorithm/string.hpp> 12 #include <boost/algorithm/string/predicate.hpp> 13 #include <boost/asio/io_context.hpp> 14 #include <boost/asio/ip/tcp.hpp> 15 #include <boost/asio/ssl/stream.hpp> 16 #include <boost/beast/core/flat_static_buffer.hpp> 17 #include <boost/beast/ssl/ssl_stream.hpp> 18 #include <boost/beast/websocket.hpp> 19 #include <json_html_serializer.hpp> 20 #include <security_headers.hpp> 21 #include <ssl_key_handler.hpp> 22 23 #include <atomic> 24 #include <chrono> 25 #include <vector> 26 27 namespace crow 28 { 29 30 inline void prettyPrintJson(crow::Response& res) 31 { 32 json_html_util::dumpHtml(res.body(), res.jsonValue); 33 34 res.addHeader("Content-Type", "text/html;charset=UTF-8"); 35 } 36 37 #ifdef BMCWEB_ENABLE_DEBUG 38 static std::atomic<int> connectionCount; 39 #endif 40 41 // request body limit size set by the BMCWEB_HTTP_REQ_BODY_LIMIT_MB option 42 constexpr unsigned int httpReqBodyLimit = 43 1024 * 1024 * BMCWEB_HTTP_REQ_BODY_LIMIT_MB; 44 45 constexpr uint64_t loggedOutPostBodyLimit = 4096; 46 47 constexpr uint32_t httpHeaderLimit = 8192; 48 49 // drop all connections after 1 minute, this time limit was chosen 50 // arbitrarily and can be adjusted later if needed 51 static constexpr const size_t loggedInAttempts = 52 (60 / timerQueueTimeoutSeconds); 53 54 static constexpr const size_t loggedOutAttempts = 55 (15 / timerQueueTimeoutSeconds); 56 57 template <typename Adaptor, typename Handler> 58 class Connection : 59 public std::enable_shared_from_this<Connection<Adaptor, Handler>> 60 { 61 public: 62 Connection(Handler* handlerIn, 63 std::function<std::string()>& getCachedDateStrF, 64 detail::TimerQueue& timerQueueIn, Adaptor adaptorIn) : 65 adaptor(std::move(adaptorIn)), 66 handler(handlerIn), getCachedDateStr(getCachedDateStrF), 67 timerQueue(timerQueueIn) 68 { 69 parser.emplace(std::piecewise_construct, std::make_tuple()); 70 parser->body_limit(httpReqBodyLimit); 71 parser->header_limit(httpHeaderLimit); 72 req.emplace(parser->get()); 73 74 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 75 std::error_code error; 76 std::filesystem::path caPath(ensuressl::trustStorePath); 77 auto caAvailable = !std::filesystem::is_empty(caPath, error); 78 caAvailable = caAvailable && !error; 79 if (caAvailable && persistent_data::SessionStore::getInstance() 80 .getAuthMethodsConfig() 81 .tls) 82 { 83 adaptor.set_verify_mode(boost::asio::ssl::verify_peer); 84 std::string id = "bmcweb"; 85 int ret = SSL_set_session_id_context( 86 adaptor.native_handle(), 87 reinterpret_cast<const unsigned char*>(id.c_str()), 88 static_cast<unsigned int>(id.length())); 89 if (ret == 0) 90 { 91 BMCWEB_LOG_ERROR << this << " failed to set SSL id"; 92 } 93 } 94 95 adaptor.set_verify_callback([this]( 96 bool preverified, 97 boost::asio::ssl::verify_context& ctx) { 98 // do nothing if TLS is disabled 99 if (!persistent_data::SessionStore::getInstance() 100 .getAuthMethodsConfig() 101 .tls) 102 { 103 BMCWEB_LOG_DEBUG << this << " TLS auth_config is disabled"; 104 return true; 105 } 106 107 // We always return true to allow full auth flow 108 if (!preverified) 109 { 110 BMCWEB_LOG_DEBUG << this << " TLS preverification failed."; 111 return true; 112 } 113 114 X509_STORE_CTX* cts = ctx.native_handle(); 115 if (cts == nullptr) 116 { 117 BMCWEB_LOG_DEBUG << this << " Cannot get native TLS handle."; 118 return true; 119 } 120 121 // Get certificate 122 X509* peerCert = 123 X509_STORE_CTX_get_current_cert(ctx.native_handle()); 124 if (peerCert == nullptr) 125 { 126 BMCWEB_LOG_DEBUG << this 127 << " Cannot get current TLS certificate."; 128 return true; 129 } 130 131 // Check if certificate is OK 132 int error = X509_STORE_CTX_get_error(cts); 133 if (error != X509_V_OK) 134 { 135 BMCWEB_LOG_INFO << this << " Last TLS error is: " << error; 136 return true; 137 } 138 // Check that we have reached final certificate in chain 139 int32_t depth = X509_STORE_CTX_get_error_depth(cts); 140 if (depth != 0) 141 142 { 143 BMCWEB_LOG_DEBUG 144 << this << " Certificate verification in progress (depth " 145 << depth << "), waiting to reach final depth"; 146 return true; 147 } 148 149 BMCWEB_LOG_DEBUG << this 150 << " Certificate verification of final depth"; 151 152 // Verify KeyUsage 153 bool isKeyUsageDigitalSignature = false; 154 bool isKeyUsageKeyAgreement = false; 155 156 ASN1_BIT_STRING* usage = static_cast<ASN1_BIT_STRING*>( 157 X509_get_ext_d2i(peerCert, NID_key_usage, nullptr, nullptr)); 158 159 if (usage == nullptr) 160 { 161 BMCWEB_LOG_DEBUG << this << " TLS usage is null"; 162 return true; 163 } 164 165 for (int i = 0; i < usage->length; i++) 166 { 167 if (KU_DIGITAL_SIGNATURE & usage->data[i]) 168 { 169 isKeyUsageDigitalSignature = true; 170 } 171 if (KU_KEY_AGREEMENT & usage->data[i]) 172 { 173 isKeyUsageKeyAgreement = true; 174 } 175 } 176 177 if (!isKeyUsageDigitalSignature || !isKeyUsageKeyAgreement) 178 { 179 BMCWEB_LOG_DEBUG << this 180 << " Certificate ExtendedKeyUsage does " 181 "not allow provided certificate to " 182 "be used for user authentication"; 183 return true; 184 } 185 ASN1_BIT_STRING_free(usage); 186 187 // Determine that ExtendedKeyUsage includes Client Auth 188 189 stack_st_ASN1_OBJECT* extUsage = 190 static_cast<stack_st_ASN1_OBJECT*>(X509_get_ext_d2i( 191 peerCert, NID_ext_key_usage, nullptr, nullptr)); 192 193 if (extUsage == nullptr) 194 { 195 BMCWEB_LOG_DEBUG << this << " TLS extUsage is null"; 196 return true; 197 } 198 199 bool isExKeyUsageClientAuth = false; 200 for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++) 201 { 202 if (NID_client_auth == 203 OBJ_obj2nid(sk_ASN1_OBJECT_value(extUsage, i))) 204 { 205 isExKeyUsageClientAuth = true; 206 break; 207 } 208 } 209 sk_ASN1_OBJECT_free(extUsage); 210 211 // Certificate has to have proper key usages set 212 if (!isExKeyUsageClientAuth) 213 { 214 BMCWEB_LOG_DEBUG << this 215 << " Certificate ExtendedKeyUsage does " 216 "not allow provided certificate to " 217 "be used for user authentication"; 218 return true; 219 } 220 std::string sslUser; 221 // Extract username contained in CommonName 222 sslUser.resize(256, '\0'); 223 224 int status = X509_NAME_get_text_by_NID( 225 X509_get_subject_name(peerCert), NID_commonName, sslUser.data(), 226 static_cast<int>(sslUser.size())); 227 228 if (status == -1) 229 { 230 BMCWEB_LOG_DEBUG 231 << this << " TLS cannot get username to create session"; 232 return true; 233 } 234 235 size_t lastChar = sslUser.find('\0'); 236 if (lastChar == std::string::npos || lastChar == 0) 237 { 238 BMCWEB_LOG_DEBUG << this << " Invalid TLS user name"; 239 return true; 240 } 241 sslUser.resize(lastChar); 242 243 session = 244 persistent_data::SessionStore::getInstance() 245 .generateUserSession( 246 sslUser, persistent_data::PersistenceType::TIMEOUT, 247 false, req->ipAddress.to_string()); 248 if (auto sp = session.lock()) 249 { 250 BMCWEB_LOG_DEBUG << this 251 << " Generating TLS session: " << sp->uniqueId; 252 } 253 return true; 254 }); 255 #endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 256 257 #ifdef BMCWEB_ENABLE_DEBUG 258 connectionCount++; 259 BMCWEB_LOG_DEBUG << this << " Connection open, total " 260 << connectionCount; 261 #endif 262 } 263 264 ~Connection() 265 { 266 res.completeRequestHandler = nullptr; 267 cancelDeadlineTimer(); 268 #ifdef BMCWEB_ENABLE_DEBUG 269 connectionCount--; 270 BMCWEB_LOG_DEBUG << this << " Connection closed, total " 271 << connectionCount; 272 #endif 273 } 274 275 Adaptor& socket() 276 { 277 return adaptor; 278 } 279 280 void start() 281 { 282 283 startDeadline(0); 284 285 // Fetch the client IP address 286 readClientIp(); 287 288 // TODO(ed) Abstract this to a more clever class with the idea of an 289 // asynchronous "start" 290 if constexpr (std::is_same_v<Adaptor, 291 boost::beast::ssl_stream< 292 boost::asio::ip::tcp::socket>>) 293 { 294 adaptor.async_handshake(boost::asio::ssl::stream_base::server, 295 [this, self(shared_from_this())]( 296 const boost::system::error_code& ec) { 297 if (ec) 298 { 299 return; 300 } 301 doReadHeaders(); 302 }); 303 } 304 else 305 { 306 doReadHeaders(); 307 } 308 } 309 310 void handle() 311 { 312 cancelDeadlineTimer(); 313 314 bool isInvalidRequest = false; 315 316 // Check for HTTP version 1.1. 317 if (req->version() == 11) 318 { 319 if (req->getHeaderValue(boost::beast::http::field::host).empty()) 320 { 321 isInvalidRequest = true; 322 res.result(boost::beast::http::status::bad_request); 323 } 324 } 325 326 BMCWEB_LOG_INFO << "Request: " 327 << " " << this << " HTTP/" << req->version() / 10 << "." 328 << req->version() % 10 << ' ' << req->methodString() 329 << " " << req->target() << " " << req->ipAddress; 330 331 needToCallAfterHandlers = false; 332 333 if (!isInvalidRequest) 334 { 335 res.completeRequestHandler = [] {}; 336 res.isAliveHelper = [this]() -> bool { return isAlive(); }; 337 338 req->ioService = static_cast<decltype(req->ioService)>( 339 &adaptor.get_executor().context()); 340 341 if (!res.completed) 342 { 343 needToCallAfterHandlers = true; 344 res.completeRequestHandler = [self(shared_from_this())] { 345 boost::asio::post(self->adaptor.get_executor(), 346 [self] { self->completeRequest(); }); 347 }; 348 if (req->isUpgrade() && 349 boost::iequals( 350 req->getHeaderValue(boost::beast::http::field::upgrade), 351 "websocket")) 352 { 353 handler->handleUpgrade(*req, res, std::move(adaptor)); 354 // delete lambda with self shared_ptr 355 // to enable connection destruction 356 res.completeRequestHandler = nullptr; 357 return; 358 } 359 handler->handle(*req, res); 360 } 361 else 362 { 363 completeRequest(); 364 } 365 } 366 else 367 { 368 completeRequest(); 369 } 370 } 371 372 bool isAlive() 373 { 374 375 if constexpr (std::is_same_v<Adaptor, 376 boost::beast::ssl_stream< 377 boost::asio::ip::tcp::socket>>) 378 { 379 return adaptor.next_layer().is_open(); 380 } 381 else 382 { 383 return adaptor.is_open(); 384 } 385 } 386 void close() 387 { 388 if constexpr (std::is_same_v<Adaptor, 389 boost::beast::ssl_stream< 390 boost::asio::ip::tcp::socket>>) 391 { 392 adaptor.next_layer().close(); 393 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 394 if (auto sp = session.lock()) 395 { 396 BMCWEB_LOG_DEBUG << this 397 << " Removing TLS session: " << sp->uniqueId; 398 persistent_data::SessionStore::getInstance().removeSession(sp); 399 } 400 #endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 401 } 402 else 403 { 404 adaptor.close(); 405 } 406 } 407 408 void completeRequest() 409 { 410 BMCWEB_LOG_INFO << "Response: " << this << ' ' << req->url << ' ' 411 << res.resultInt() << " keepalive=" << req->keepAlive(); 412 413 addSecurityHeaders(res); 414 415 if (needToCallAfterHandlers) 416 { 417 crow::authorization::cleanupTempSession(*req); 418 } 419 420 if (!isAlive()) 421 { 422 // BMCWEB_LOG_DEBUG << this << " delete (socket is closed) " << 423 // isReading 424 // << ' ' << isWriting; 425 // delete this; 426 427 // delete lambda with self shared_ptr 428 // to enable connection destruction 429 res.completeRequestHandler = nullptr; 430 return; 431 } 432 if (res.body().empty() && !res.jsonValue.empty()) 433 { 434 if (http_helpers::requestPrefersHtml(*req)) 435 { 436 prettyPrintJson(res); 437 } 438 else 439 { 440 res.jsonMode(); 441 res.body() = res.jsonValue.dump(2, ' ', true); 442 } 443 } 444 445 if (res.resultInt() >= 400 && res.body().empty()) 446 { 447 res.body() = std::string(res.reason()); 448 } 449 450 if (res.result() == boost::beast::http::status::no_content) 451 { 452 // Boost beast throws if content is provided on a no-content 453 // response. Ideally, this would never happen, but in the case that 454 // it does, we don't want to throw. 455 BMCWEB_LOG_CRITICAL 456 << this << " Response content provided but code was no-content"; 457 res.body().clear(); 458 } 459 460 res.addHeader(boost::beast::http::field::date, getCachedDateStr()); 461 462 res.keepAlive(req->keepAlive()); 463 464 doWrite(); 465 466 // delete lambda with self shared_ptr 467 // to enable connection destruction 468 res.completeRequestHandler = nullptr; 469 } 470 471 void readClientIp() 472 { 473 boost::system::error_code ec; 474 BMCWEB_LOG_DEBUG << "Fetch the client IP address"; 475 boost::asio::ip::tcp::endpoint endpoint = 476 boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec); 477 478 if (ec) 479 { 480 // If remote endpoint fails keep going. "ClientOriginIPAddress" 481 // will be empty. 482 BMCWEB_LOG_ERROR << "Failed to get the client's IP Address. ec : " 483 << ec; 484 } 485 else 486 { 487 req->ipAddress = endpoint.address(); 488 } 489 } 490 491 private: 492 void doReadHeaders() 493 { 494 BMCWEB_LOG_DEBUG << this << " doReadHeaders"; 495 496 // Clean up any previous Connection. 497 boost::beast::http::async_read_header( 498 adaptor, buffer, *parser, 499 [this, 500 self(shared_from_this())](const boost::system::error_code& ec, 501 std::size_t bytesTransferred) { 502 BMCWEB_LOG_ERROR << this << " async_read_header " 503 << bytesTransferred << " Bytes"; 504 bool errorWhileReading = false; 505 if (ec) 506 { 507 errorWhileReading = true; 508 BMCWEB_LOG_ERROR 509 << this << " Error while reading: " << ec.message(); 510 } 511 else 512 { 513 // if the adaptor isn't open anymore, and wasn't handed to a 514 // websocket, treat as an error 515 if (!isAlive() && !req->isUpgrade()) 516 { 517 errorWhileReading = true; 518 } 519 } 520 521 cancelDeadlineTimer(); 522 523 if (errorWhileReading) 524 { 525 close(); 526 BMCWEB_LOG_DEBUG << this << " from read(1)"; 527 return; 528 } 529 530 if (!req) 531 { 532 close(); 533 return; 534 } 535 536 // Note, despite the bmcweb coding policy on use of exceptions 537 // for error handling, this one particular use of exceptions is 538 // deemed acceptible, as it solved a significant error handling 539 // problem that resulted in seg faults, the exact thing that the 540 // exceptions rule is trying to avoid. If at some point, 541 // boost::urls makes the parser object public (or we port it 542 // into bmcweb locally) this will be replaced with 543 // parser::parse, which returns a status code 544 545 try 546 { 547 req->urlView = boost::urls::url_view(req->target()); 548 req->url = req->urlView.encoded_path(); 549 } 550 catch (std::exception& p) 551 { 552 BMCWEB_LOG_ERROR << p.what(); 553 } 554 555 crow::authorization::authenticate(*req, res, session); 556 557 bool loggedIn = req && req->session; 558 if (loggedIn) 559 { 560 startDeadline(loggedInAttempts); 561 BMCWEB_LOG_DEBUG << "Starting slow deadline"; 562 563 req->urlParams = req->urlView.params(); 564 565 #ifdef BMCWEB_ENABLE_DEBUG 566 std::string paramList = ""; 567 for (const auto param : req->urlParams) 568 { 569 paramList += param->key() + " " + param->value() + " "; 570 } 571 BMCWEB_LOG_DEBUG << "QueryParams: " << paramList; 572 #endif 573 } 574 else 575 { 576 const boost::optional<uint64_t> contentLength = 577 parser->content_length(); 578 if (contentLength && 579 *contentLength > loggedOutPostBodyLimit) 580 { 581 BMCWEB_LOG_DEBUG << "Content length greater than limit " 582 << *contentLength; 583 close(); 584 return; 585 } 586 587 startDeadline(loggedOutAttempts); 588 BMCWEB_LOG_DEBUG << "Starting quick deadline"; 589 } 590 doRead(); 591 }); 592 } 593 594 void doRead() 595 { 596 BMCWEB_LOG_DEBUG << this << " doRead"; 597 598 boost::beast::http::async_read( 599 adaptor, buffer, *parser, 600 [this, 601 self(shared_from_this())](const boost::system::error_code& ec, 602 std::size_t bytesTransferred) { 603 BMCWEB_LOG_DEBUG << this << " async_read " << bytesTransferred 604 << " Bytes"; 605 606 bool errorWhileReading = false; 607 if (ec) 608 { 609 BMCWEB_LOG_ERROR 610 << this << " Error while reading: " << ec.message(); 611 errorWhileReading = true; 612 } 613 else 614 { 615 if (isAlive()) 616 { 617 cancelDeadlineTimer(); 618 bool loggedIn = req && req->session; 619 if (loggedIn) 620 { 621 startDeadline(loggedInAttempts); 622 } 623 else 624 { 625 startDeadline(loggedOutAttempts); 626 } 627 } 628 else 629 { 630 errorWhileReading = true; 631 } 632 } 633 if (errorWhileReading) 634 { 635 cancelDeadlineTimer(); 636 close(); 637 BMCWEB_LOG_DEBUG << this << " from read(1)"; 638 return; 639 } 640 handle(); 641 }); 642 } 643 644 void doWrite() 645 { 646 bool loggedIn = req && req->session; 647 if (loggedIn) 648 { 649 startDeadline(loggedInAttempts); 650 } 651 else 652 { 653 startDeadline(loggedOutAttempts); 654 } 655 BMCWEB_LOG_DEBUG << this << " doWrite"; 656 res.preparePayload(); 657 serializer.emplace(*res.stringResponse); 658 boost::beast::http::async_write( 659 adaptor, *serializer, 660 [this, 661 self(shared_from_this())](const boost::system::error_code& ec, 662 std::size_t bytesTransferred) { 663 BMCWEB_LOG_DEBUG << this << " async_write " << bytesTransferred 664 << " bytes"; 665 666 cancelDeadlineTimer(); 667 668 if (ec) 669 { 670 BMCWEB_LOG_DEBUG << this << " from write(2)"; 671 return; 672 } 673 if (!res.keepAlive()) 674 { 675 close(); 676 BMCWEB_LOG_DEBUG << this << " from write(1)"; 677 return; 678 } 679 680 serializer.reset(); 681 BMCWEB_LOG_DEBUG << this << " Clearing response"; 682 res.clear(); 683 parser.emplace(std::piecewise_construct, std::make_tuple()); 684 parser->body_limit(httpReqBodyLimit); // reset body limit for 685 // newly created parser 686 buffer.consume(buffer.size()); 687 688 req.emplace(parser->get()); 689 doReadHeaders(); 690 }); 691 } 692 693 void cancelDeadlineTimer() 694 { 695 if (timerCancelKey) 696 { 697 BMCWEB_LOG_DEBUG << this << " timer cancelled: " << &timerQueue 698 << ' ' << *timerCancelKey; 699 timerQueue.cancel(*timerCancelKey); 700 timerCancelKey.reset(); 701 } 702 } 703 704 void startDeadline(size_t timerIterations) 705 { 706 cancelDeadlineTimer(); 707 708 if (timerIterations) 709 { 710 timerIterations--; 711 } 712 713 timerCancelKey = 714 timerQueue.add([self(shared_from_this()), timerIterations, 715 readCount{parser->get().body().size()}] { 716 // Mark timer as not active to avoid canceling it during 717 // Connection destructor which leads to double free issue 718 self->timerCancelKey.reset(); 719 if (!self->isAlive()) 720 { 721 return; 722 } 723 724 bool loggedIn = self->req && self->req->session; 725 // allow slow uploads for logged in users 726 if (loggedIn && self->parser->get().body().size() > readCount) 727 { 728 BMCWEB_LOG_DEBUG << self.get() 729 << " restart timer - read in progress"; 730 self->startDeadline(timerIterations); 731 return; 732 } 733 734 // Threshold can be used to drop slow connections 735 // to protect against slow-rate DoS attack 736 if (timerIterations) 737 { 738 BMCWEB_LOG_DEBUG << self.get() << " restart timer"; 739 self->startDeadline(timerIterations); 740 return; 741 } 742 743 self->close(); 744 }); 745 746 if (!timerCancelKey) 747 { 748 close(); 749 return; 750 } 751 BMCWEB_LOG_DEBUG << this << " timer added: " << &timerQueue << ' ' 752 << *timerCancelKey; 753 } 754 755 private: 756 Adaptor adaptor; 757 Handler* handler; 758 759 // Making this a std::optional allows it to be efficiently destroyed and 760 // re-created on Connection reset 761 std::optional< 762 boost::beast::http::request_parser<boost::beast::http::string_body>> 763 parser; 764 765 boost::beast::flat_static_buffer<8192> buffer; 766 767 std::optional<boost::beast::http::response_serializer< 768 boost::beast::http::string_body>> 769 serializer; 770 771 std::optional<crow::Request> req; 772 crow::Response res; 773 774 std::weak_ptr<persistent_data::UserSession> session; 775 776 std::optional<size_t> timerCancelKey; 777 778 bool needToCallAfterHandlers{}; 779 bool needToStartReadAfterComplete{}; 780 781 std::function<std::string()>& getCachedDateStr; 782 detail::TimerQueue& timerQueue; 783 784 using std::enable_shared_from_this< 785 Connection<Adaptor, Handler>>::shared_from_this; 786 }; 787 } // namespace crow 788