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