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