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 "http_body.hpp" 8 #include "http_response.hpp" 9 #include "http_utility.hpp" 10 #include "logging.hpp" 11 #include "mutual_tls.hpp" 12 #include "nghttp2_adapters.hpp" 13 #include "ssl_key_handler.hpp" 14 #include "utility.hpp" 15 16 #include <boost/asio/io_context.hpp> 17 #include <boost/asio/ip/tcp.hpp> 18 #include <boost/asio/ssl/stream.hpp> 19 #include <boost/asio/steady_timer.hpp> 20 #include <boost/beast/http/error.hpp> 21 #include <boost/beast/http/parser.hpp> 22 #include <boost/beast/http/read.hpp> 23 #include <boost/beast/http/serializer.hpp> 24 #include <boost/beast/http/write.hpp> 25 #include <boost/beast/websocket.hpp> 26 #include <boost/system/error_code.hpp> 27 28 #include <array> 29 #include <atomic> 30 #include <chrono> 31 #include <functional> 32 #include <memory> 33 #include <vector> 34 35 namespace crow 36 { 37 38 struct Http2StreamData 39 { 40 std::shared_ptr<Request> req = std::make_shared<Request>(); 41 std::optional<bmcweb::HttpBody::reader> reqReader; 42 Response res; 43 std::optional<bmcweb::HttpBody::writer> writer; 44 }; 45 46 template <typename Adaptor, typename Handler> 47 class HTTP2Connection : 48 public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>> 49 { 50 using self_type = HTTP2Connection<Adaptor, Handler>; 51 52 public: 53 HTTP2Connection(Adaptor&& adaptorIn, Handler* handlerIn, 54 std::function<std::string()>& getCachedDateStrF) : 55 adaptor(std::move(adaptorIn)), 56 ngSession(initializeNghttp2Session()), handler(handlerIn), 57 getCachedDateStr(getCachedDateStrF) 58 {} 59 60 void start() 61 { 62 // Create the control stream 63 streams[0]; 64 65 if (sendServerConnectionHeader() != 0) 66 { 67 BMCWEB_LOG_ERROR("send_server_connection_header failed"); 68 return; 69 } 70 doRead(); 71 } 72 73 int sendServerConnectionHeader() 74 { 75 BMCWEB_LOG_DEBUG("send_server_connection_header()"); 76 77 uint32_t maxStreams = 4; 78 std::array<nghttp2_settings_entry, 2> iv = { 79 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams}, 80 {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}}; 81 int rv = ngSession.submitSettings(iv); 82 if (rv != 0) 83 { 84 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv)); 85 return -1; 86 } 87 writeBuffer(); 88 return 0; 89 } 90 91 static ssize_t fileReadCallback(nghttp2_session* /* session */, 92 int32_t streamId, uint8_t* buf, 93 size_t length, uint32_t* dataFlags, 94 nghttp2_data_source* /*source*/, 95 void* userPtr) 96 { 97 self_type& self = userPtrToSelf(userPtr); 98 99 auto streamIt = self.streams.find(streamId); 100 if (streamIt == self.streams.end()) 101 { 102 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 103 } 104 Http2StreamData& stream = streamIt->second; 105 BMCWEB_LOG_DEBUG("File read callback length: {}", length); 106 if (!stream.writer) 107 { 108 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 109 } 110 boost::beast::error_code ec; 111 boost::optional<std::pair<boost::asio::const_buffer, bool>> out = 112 stream.writer->getWithMaxSize(ec, length); 113 if (ec) 114 { 115 BMCWEB_LOG_CRITICAL("Failed to get buffer"); 116 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 117 } 118 if (!out) 119 { 120 BMCWEB_LOG_ERROR("Empty file, setting EOF"); 121 *dataFlags |= NGHTTP2_DATA_FLAG_EOF; 122 return 0; 123 } 124 125 BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size()); 126 if (length < out->first.size()) 127 { 128 BMCWEB_LOG_CRITICAL( 129 "Buffer overflow that should never happen happened"); 130 // Should never happen because of length limit on get() above 131 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 132 } 133 boost::asio::mutable_buffer writeableBuf(buf, length); 134 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size()); 135 size_t copied = boost::asio::buffer_copy(writeableBuf, out->first); 136 if (copied != out->first.size()) 137 { 138 BMCWEB_LOG_ERROR( 139 "Couldn't copy all {} bytes into buffer, only copied {}", 140 out->first.size(), copied); 141 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 142 } 143 144 if (!out->second) 145 { 146 BMCWEB_LOG_DEBUG("Setting EOF flag"); 147 *dataFlags |= NGHTTP2_DATA_FLAG_EOF; 148 } 149 return static_cast<ssize_t>(copied); 150 } 151 152 nghttp2_nv headerFromStringViews(std::string_view name, 153 std::string_view value, uint8_t flags) 154 { 155 uint8_t* nameData = std::bit_cast<uint8_t*>(name.data()); 156 uint8_t* valueData = std::bit_cast<uint8_t*>(value.data()); 157 return {nameData, valueData, name.size(), value.size(), flags}; 158 } 159 160 int sendResponse(Response& completedRes, int32_t streamId) 161 { 162 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId); 163 164 auto it = streams.find(streamId); 165 if (it == streams.end()) 166 { 167 close(); 168 return -1; 169 } 170 Http2StreamData& stream = it->second; 171 Response& res = stream.res; 172 res = std::move(completedRes); 173 crow::Request& thisReq = *stream.req; 174 175 completeResponseFields(thisReq, res); 176 res.addHeader(boost::beast::http::field::date, getCachedDateStr()); 177 res.preparePayload(); 178 179 boost::beast::http::fields& fields = res.fields(); 180 std::string code = std::to_string(res.resultInt()); 181 std::vector<nghttp2_nv> hdr; 182 hdr.emplace_back( 183 headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE)); 184 for (const boost::beast::http::fields::value_type& header : fields) 185 { 186 hdr.emplace_back(headerFromStringViews( 187 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE)); 188 } 189 http::response<bmcweb::HttpBody>& fbody = res.response; 190 stream.writer.emplace(fbody.base(), fbody.body()); 191 192 nghttp2_data_provider dataPrd{ 193 .source = {.fd = 0}, 194 .read_callback = fileReadCallback, 195 }; 196 197 int rv = ngSession.submitResponse(streamId, hdr, &dataPrd); 198 if (rv != 0) 199 { 200 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv)); 201 close(); 202 return -1; 203 } 204 writeBuffer(); 205 206 return 0; 207 } 208 209 nghttp2_session initializeNghttp2Session() 210 { 211 nghttp2_session_callbacks callbacks; 212 callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic); 213 callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic); 214 callbacks.setOnHeaderCallback(onHeaderCallbackStatic); 215 callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic); 216 callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic); 217 218 nghttp2_session session(callbacks); 219 session.setUserData(this); 220 221 return session; 222 } 223 224 int onRequestRecv(int32_t streamId) 225 { 226 BMCWEB_LOG_DEBUG("on_request_recv"); 227 228 auto it = streams.find(streamId); 229 if (it == streams.end()) 230 { 231 close(); 232 return -1; 233 } 234 auto& reqReader = it->second.reqReader; 235 if (reqReader) 236 { 237 boost::beast::error_code ec; 238 reqReader->finish(ec); 239 if (ec) 240 { 241 BMCWEB_LOG_CRITICAL("Failed to finalize payload"); 242 close(); 243 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 244 } 245 } 246 crow::Request& thisReq = *it->second.req; 247 thisReq.ioService = static_cast<decltype(thisReq.ioService)>( 248 &adaptor.get_executor().context()); 249 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq), 250 thisReq.url().encoded_path()); 251 252 crow::Response& thisRes = it->second.res; 253 254 thisRes.setCompleteRequestHandler( 255 [this, streamId](Response& completeRes) { 256 BMCWEB_LOG_DEBUG("res.completeRequestHandler called"); 257 if (sendResponse(completeRes, streamId) != 0) 258 { 259 close(); 260 return; 261 } 262 }); 263 auto asyncResp = 264 std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res)); 265 #ifndef BMCWEB_INSECURE_DISABLE_AUTHX 266 thisReq.session = crow::authentication::authenticate( 267 {}, asyncResp->res, thisReq.method(), thisReq.req, nullptr); 268 if (!crow::authentication::isOnAllowlist(thisReq.url().path(), 269 thisReq.method()) && 270 thisReq.session == nullptr) 271 { 272 BMCWEB_LOG_WARNING("Authentication failed"); 273 forward_unauthorized::sendUnauthorized( 274 thisReq.url().encoded_path(), 275 thisReq.getHeaderValue("X-Requested-With"), 276 thisReq.getHeaderValue("Accept"), asyncResp->res); 277 } 278 else 279 #endif // BMCWEB_INSECURE_DISABLE_AUTHX 280 { 281 std::string_view expected = thisReq.getHeaderValue( 282 boost::beast::http::field::if_none_match); 283 BMCWEB_LOG_DEBUG("Setting expected hash {}", expected); 284 if (!expected.empty()) 285 { 286 asyncResp->res.setExpectedHash(expected); 287 } 288 handler->handle(it->second.req, asyncResp); 289 } 290 return 0; 291 } 292 293 int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId, 294 const uint8_t* data, size_t len) 295 { 296 auto thisStream = streams.find(streamId); 297 if (thisStream == streams.end()) 298 { 299 BMCWEB_LOG_ERROR("Unknown stream{}", streamId); 300 close(); 301 return -1; 302 } 303 304 std::optional<bmcweb::HttpBody::reader>& reqReader = 305 thisStream->second.reqReader; 306 if (!reqReader) 307 { 308 reqReader.emplace( 309 bmcweb::HttpBody::reader(thisStream->second.req->req.base(), 310 thisStream->second.req->req.body())); 311 } 312 boost::beast::error_code ec; 313 reqReader->put(boost::asio::const_buffer(data, len), ec); 314 if (ec) 315 { 316 BMCWEB_LOG_CRITICAL("Failed to write payload"); 317 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 318 } 319 return 0; 320 } 321 322 static int onDataChunkRecvStatic(nghttp2_session* /* session */, 323 uint8_t flags, int32_t streamId, 324 const uint8_t* data, size_t len, 325 void* userData) 326 { 327 BMCWEB_LOG_DEBUG("on_frame_recv_callback"); 328 if (userData == nullptr) 329 { 330 BMCWEB_LOG_CRITICAL("user data was null?"); 331 return NGHTTP2_ERR_CALLBACK_FAILURE; 332 } 333 return userPtrToSelf(userData).onDataChunkRecvCallback(flags, streamId, 334 data, len); 335 } 336 337 int onFrameRecvCallback(const nghttp2_frame& frame) 338 { 339 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type)); 340 switch (frame.hd.type) 341 { 342 case NGHTTP2_DATA: 343 case NGHTTP2_HEADERS: 344 // Check that the client request has finished 345 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0) 346 { 347 return onRequestRecv(frame.hd.stream_id); 348 } 349 break; 350 default: 351 break; 352 } 353 return 0; 354 } 355 356 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */, 357 const nghttp2_frame* frame, 358 void* userData) 359 { 360 BMCWEB_LOG_DEBUG("on_frame_recv_callback"); 361 if (userData == nullptr) 362 { 363 BMCWEB_LOG_CRITICAL("user data was null?"); 364 return NGHTTP2_ERR_CALLBACK_FAILURE; 365 } 366 if (frame == nullptr) 367 { 368 BMCWEB_LOG_CRITICAL("frame was null?"); 369 return NGHTTP2_ERR_CALLBACK_FAILURE; 370 } 371 return userPtrToSelf(userData).onFrameRecvCallback(*frame); 372 } 373 374 static self_type& userPtrToSelf(void* userData) 375 { 376 // This method exists to keep the unsafe reinterpret cast in one 377 // place. 378 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 379 return *reinterpret_cast<self_type*>(userData); 380 } 381 382 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */, 383 int32_t streamId, 384 uint32_t /*unused*/, void* userData) 385 { 386 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId); 387 if (userData == nullptr) 388 { 389 BMCWEB_LOG_CRITICAL("user data was null?"); 390 return NGHTTP2_ERR_CALLBACK_FAILURE; 391 } 392 if (userPtrToSelf(userData).streams.erase(streamId) <= 0) 393 { 394 return -1; 395 } 396 return 0; 397 } 398 399 int onHeaderCallback(const nghttp2_frame& frame, 400 std::span<const uint8_t> name, 401 std::span<const uint8_t> value) 402 { 403 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 404 std::string_view nameSv(reinterpret_cast<const char*>(name.data()), 405 name.size()); 406 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) 407 std::string_view valueSv(reinterpret_cast<const char*>(value.data()), 408 value.size()); 409 410 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv, 411 valueSv); 412 if (frame.hd.type != NGHTTP2_HEADERS) 413 { 414 return 0; 415 } 416 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST) 417 { 418 return 0; 419 } 420 auto thisStream = streams.find(frame.hd.stream_id); 421 if (thisStream == streams.end()) 422 { 423 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id); 424 close(); 425 return -1; 426 } 427 428 crow::Request& thisReq = *thisStream->second.req; 429 430 if (nameSv == ":path") 431 { 432 thisReq.target(valueSv); 433 } 434 else if (nameSv == ":method") 435 { 436 boost::beast::http::verb verb = 437 boost::beast::http::string_to_verb(valueSv); 438 if (verb == boost::beast::http::verb::unknown) 439 { 440 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv); 441 close(); 442 return -1; 443 } 444 thisReq.method(verb); 445 } 446 else if (nameSv == ":scheme") 447 { 448 // Nothing to check on scheme 449 } 450 else 451 { 452 thisReq.addHeader(nameSv, valueSv); 453 } 454 return 0; 455 } 456 457 static int onHeaderCallbackStatic(nghttp2_session* /* session */, 458 const nghttp2_frame* frame, 459 const uint8_t* name, size_t namelen, 460 const uint8_t* value, size_t vallen, 461 uint8_t /* flags */, void* userData) 462 { 463 if (userData == nullptr) 464 { 465 BMCWEB_LOG_CRITICAL("user data was null?"); 466 return NGHTTP2_ERR_CALLBACK_FAILURE; 467 } 468 if (frame == nullptr) 469 { 470 BMCWEB_LOG_CRITICAL("frame was null?"); 471 return NGHTTP2_ERR_CALLBACK_FAILURE; 472 } 473 if (name == nullptr) 474 { 475 BMCWEB_LOG_CRITICAL("name was null?"); 476 return NGHTTP2_ERR_CALLBACK_FAILURE; 477 } 478 if (value == nullptr) 479 { 480 BMCWEB_LOG_CRITICAL("value was null?"); 481 return NGHTTP2_ERR_CALLBACK_FAILURE; 482 } 483 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen}, 484 {value, vallen}); 485 } 486 487 int onBeginHeadersCallback(const nghttp2_frame& frame) 488 { 489 if (frame.hd.type == NGHTTP2_HEADERS && 490 frame.headers.cat == NGHTTP2_HCAT_REQUEST) 491 { 492 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id); 493 494 Http2StreamData& stream = streams[frame.hd.stream_id]; 495 // http2 is by definition always tls 496 stream.req->isSecure = true; 497 } 498 return 0; 499 } 500 501 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */, 502 const nghttp2_frame* frame, 503 void* userData) 504 { 505 BMCWEB_LOG_DEBUG("on_begin_headers_callback"); 506 if (userData == nullptr) 507 { 508 BMCWEB_LOG_CRITICAL("user data was null?"); 509 return NGHTTP2_ERR_CALLBACK_FAILURE; 510 } 511 if (frame == nullptr) 512 { 513 BMCWEB_LOG_CRITICAL("frame was null?"); 514 return NGHTTP2_ERR_CALLBACK_FAILURE; 515 } 516 return userPtrToSelf(userData).onBeginHeadersCallback(*frame); 517 } 518 519 static void afterWriteBuffer(const std::shared_ptr<self_type>& self, 520 const boost::system::error_code& ec, 521 size_t sendLength) 522 { 523 self->isWriting = false; 524 BMCWEB_LOG_DEBUG("Sent {}", sendLength); 525 if (ec) 526 { 527 self->close(); 528 return; 529 } 530 self->writeBuffer(); 531 } 532 533 void writeBuffer() 534 { 535 if (isWriting) 536 { 537 return; 538 } 539 std::span<const uint8_t> data = ngSession.memSend(); 540 if (data.empty()) 541 { 542 return; 543 } 544 isWriting = true; 545 boost::asio::async_write( 546 adaptor, boost::asio::const_buffer(data.data(), data.size()), 547 std::bind_front(afterWriteBuffer, shared_from_this())); 548 } 549 550 void close() 551 { 552 if constexpr (std::is_same_v<Adaptor, 553 boost::asio::ssl::stream< 554 boost::asio::ip::tcp::socket>>) 555 { 556 adaptor.next_layer().close(); 557 } 558 else 559 { 560 adaptor.close(); 561 } 562 } 563 564 void afterDoRead(const std::shared_ptr<self_type>& /*self*/, 565 const boost::system::error_code& ec, 566 size_t bytesTransferred) 567 { 568 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this), 569 bytesTransferred); 570 571 if (ec) 572 { 573 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this), 574 ec.message()); 575 close(); 576 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this)); 577 return; 578 } 579 std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred}; 580 581 ssize_t readLen = ngSession.memRecv(bufferSpan); 582 if (readLen < 0) 583 { 584 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen); 585 close(); 586 return; 587 } 588 writeBuffer(); 589 590 doRead(); 591 } 592 593 void doRead() 594 { 595 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this)); 596 adaptor.async_read_some( 597 boost::asio::buffer(inBuffer), 598 std::bind_front(&self_type::afterDoRead, this, shared_from_this())); 599 } 600 601 // A mapping from http2 stream ID to Stream Data 602 std::map<int32_t, Http2StreamData> streams; 603 604 std::array<uint8_t, 8192> inBuffer{}; 605 606 Adaptor adaptor; 607 bool isWriting = false; 608 609 nghttp2_session ngSession; 610 611 Handler* handler; 612 std::function<std::string()>& getCachedDateStr; 613 614 using std::enable_shared_from_this< 615 HTTP2Connection<Adaptor, Handler>>::shared_from_this; 616 617 using std::enable_shared_from_this< 618 HTTP2Connection<Adaptor, Handler>>::weak_from_this; 619 }; 620 } // namespace crow 621