xref: /openbmc/bmcweb/http/http_connection.hpp (revision 62b06bcc8311977f649be52ef1eef67f5ac68f03)
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 "forward_unauthorized.hpp"
10 #include "http2_connection.hpp"
11 #include "http_body.hpp"
12 #include "http_connect_types.hpp"
13 #include "http_request.hpp"
14 #include "http_response.hpp"
15 #include "http_utility.hpp"
16 #include "logging.hpp"
17 #include "mutual_tls.hpp"
18 #include "sessions.hpp"
19 #include "str_utility.hpp"
20 #include "utility.hpp"
21 
22 #include <boost/asio/error.hpp>
23 #include <boost/asio/ip/tcp.hpp>
24 #include <boost/asio/ssl/error.hpp>
25 #include <boost/asio/ssl/stream.hpp>
26 #include <boost/asio/ssl/stream_base.hpp>
27 #include <boost/asio/ssl/verify_context.hpp>
28 #include <boost/asio/steady_timer.hpp>
29 #include <boost/beast/_experimental/test/stream.hpp>
30 #include <boost/beast/core/buffers_generator.hpp>
31 #include <boost/beast/core/detect_ssl.hpp>
32 #include <boost/beast/core/error.hpp>
33 #include <boost/beast/core/flat_static_buffer.hpp>
34 #include <boost/beast/http/error.hpp>
35 #include <boost/beast/http/field.hpp>
36 #include <boost/beast/http/message_generator.hpp>
37 #include <boost/beast/http/parser.hpp>
38 #include <boost/beast/http/read.hpp>
39 #include <boost/beast/http/rfc7230.hpp>
40 #include <boost/beast/http/status.hpp>
41 #include <boost/beast/http/verb.hpp>
42 #include <boost/none.hpp>
43 #include <boost/optional/optional.hpp>
44 
45 #include <bit>
46 #include <chrono>
47 #include <cstddef>
48 #include <cstdint>
49 #include <functional>
50 #include <memory>
51 #include <optional>
52 #include <string>
53 #include <string_view>
54 #include <system_error>
55 #include <type_traits>
56 #include <utility>
57 
58 namespace crow
59 {
60 
61 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
62 static int connectionCount = 0;
63 
64 // request body limit size set by the BMCWEB_HTTP_BODY_LIMIT option
65 constexpr uint64_t httpReqBodyLimit = 1024UL * 1024UL * BMCWEB_HTTP_BODY_LIMIT;
66 
67 constexpr uint64_t loggedOutPostBodyLimit = 4096U;
68 
69 constexpr uint32_t httpHeaderLimit = 8192U;
70 
71 template <typename Adaptor, typename Handler>
72 class Connection :
73     public std::enable_shared_from_this<Connection<Adaptor, Handler>>
74 {
75     using self_type = Connection<Adaptor, Handler>;
76 
77   public:
Connection(Handler * handlerIn,HttpType httpTypeIn,boost::asio::steady_timer && timerIn,std::function<std::string ()> & getCachedDateStrF,boost::asio::ssl::stream<Adaptor> && adaptorIn)78     Connection(Handler* handlerIn, HttpType httpTypeIn,
79                boost::asio::steady_timer&& timerIn,
80                std::function<std::string()>& getCachedDateStrF,
81                boost::asio::ssl::stream<Adaptor>&& adaptorIn) :
82         httpType(httpTypeIn), adaptor(std::move(adaptorIn)), handler(handlerIn),
83         timer(std::move(timerIn)), getCachedDateStr(getCachedDateStrF)
84     {
85         initParser();
86 
87         connectionCount++;
88 
89         BMCWEB_LOG_DEBUG("{} Connection created, total {}", logPtr(this),
90                          connectionCount);
91     }
92 
~Connection()93     ~Connection()
94     {
95         res.releaseCompleteRequestHandler();
96         cancelDeadlineTimer();
97 
98         connectionCount--;
99         BMCWEB_LOG_DEBUG("{} Connection closed, total {}", logPtr(this),
100                          connectionCount);
101     }
102 
103     Connection(const Connection&) = delete;
104     Connection(Connection&&) = delete;
105     Connection& operator=(const Connection&) = delete;
106     Connection& operator=(Connection&&) = delete;
107 
tlsVerifyCallback(bool preverified,boost::asio::ssl::verify_context & ctx)108     bool tlsVerifyCallback(bool preverified,
109                            boost::asio::ssl::verify_context& ctx)
110     {
111         BMCWEB_LOG_DEBUG("{} tlsVerifyCallback called with preverified {}",
112                          logPtr(this), preverified);
113         if (preverified)
114         {
115             mtlsSession = verifyMtlsUser(ip, ctx);
116             if (mtlsSession)
117             {
118                 BMCWEB_LOG_DEBUG("{} Generated TLS session: {}", logPtr(this),
119                                  mtlsSession->uniqueId);
120             }
121         }
122         const persistent_data::AuthConfigMethods& c =
123             persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
124         if (c.tlsStrict)
125         {
126             BMCWEB_LOG_DEBUG(
127                 "{} TLS is in strict mode, returning preverified as is.",
128                 logPtr(this));
129             return preverified;
130         }
131         // If tls strict mode is disabled
132         // We always return true to allow full auth flow for resources that
133         // don't require auth
134         return true;
135     }
136 
prepareMutualTls()137     bool prepareMutualTls()
138     {
139         BMCWEB_LOG_DEBUG("prepareMutualTls");
140 
141         constexpr std::string_view id = "bmcweb";
142 
143         const char* idPtr = id.data();
144         const auto* idCPtr = std::bit_cast<const unsigned char*>(idPtr);
145         auto idLen = static_cast<unsigned int>(id.length());
146         int ret =
147             SSL_set_session_id_context(adaptor.native_handle(), idCPtr, idLen);
148         if (ret == 0)
149         {
150             BMCWEB_LOG_ERROR("{} failed to set SSL id", logPtr(this));
151             return false;
152         }
153 
154         BMCWEB_LOG_DEBUG("set_verify_callback");
155 
156         boost::system::error_code ec;
157         adaptor.set_verify_callback(
158             std::bind_front(&self_type::tlsVerifyCallback, this), ec);
159         if (ec)
160         {
161             BMCWEB_LOG_ERROR("Failed to set verify callback {}", ec);
162             return false;
163         }
164 
165         return true;
166     }
167 
afterDetectSsl(const std::shared_ptr<self_type> &,boost::beast::error_code ec,bool isTls)168     void afterDetectSsl(const std::shared_ptr<self_type>& /*self*/,
169                         boost::beast::error_code ec, bool isTls)
170     {
171         if (ec)
172         {
173             BMCWEB_LOG_ERROR("Couldn't detect ssl ", ec);
174             return;
175         }
176         BMCWEB_LOG_DEBUG("{} TLS was detected as {}", logPtr(this), isTls);
177         if (isTls)
178         {
179             if (httpType != HttpType::HTTPS && httpType != HttpType::BOTH)
180             {
181                 BMCWEB_LOG_WARNING(
182                     "{} Connection closed due to incompatible type",
183                     logPtr(this));
184                 return;
185             }
186             httpType = HttpType::HTTPS;
187             adaptor.async_handshake(
188                 boost::asio::ssl::stream_base::server, buffer.data(),
189                 std::bind_front(&self_type::afterSslHandshake, this,
190                                 shared_from_this()));
191         }
192         else
193         {
194             if (httpType != HttpType::HTTP && httpType != HttpType::BOTH)
195             {
196                 BMCWEB_LOG_WARNING(
197                     "{} Connection closed due to incompatible type",
198                     logPtr(this));
199                 return;
200             }
201 
202             httpType = HttpType::HTTP;
203             BMCWEB_LOG_INFO("Starting non-SSL session");
204             doReadHeaders();
205         }
206     }
207 
start()208     void start()
209     {
210         BMCWEB_LOG_DEBUG("{} Connection started, total {}", logPtr(this),
211                          connectionCount);
212         if (connectionCount >= 200)
213         {
214             BMCWEB_LOG_CRITICAL("{} Max connection count exceeded.",
215                                 logPtr(this));
216             return;
217         }
218 
219         if constexpr (BMCWEB_MUTUAL_TLS_AUTH)
220         {
221             if (!prepareMutualTls())
222             {
223                 BMCWEB_LOG_ERROR("{} Failed to prepare mTLS", logPtr(this));
224                 return;
225             }
226         }
227 
228         startDeadline();
229 
230         readClientIp();
231         boost::beast::async_detect_ssl(
232             adaptor.next_layer(), buffer,
233             std::bind_front(&self_type::afterDetectSsl, this,
234                             shared_from_this()));
235     }
236 
afterSslHandshake(const std::shared_ptr<self_type> &,const boost::system::error_code & ec,size_t bytesParsed)237     void afterSslHandshake(const std::shared_ptr<self_type>& /*self*/,
238                            const boost::system::error_code& ec,
239                            size_t bytesParsed)
240     {
241         buffer.consume(bytesParsed);
242         if (ec)
243         {
244             BMCWEB_LOG_ERROR("{} SSL handshake failed", logPtr(this));
245             return;
246         }
247         BMCWEB_LOG_DEBUG("{} SSL handshake succeeded", logPtr(this));
248         // If http2 is enabled, negotiate the protocol
249         if constexpr (BMCWEB_EXPERIMENTAL_HTTP2)
250         {
251             const unsigned char* alpn = nullptr;
252             unsigned int alpnlen = 0;
253             SSL_get0_alpn_selected(adaptor.native_handle(), &alpn, &alpnlen);
254             if (alpn != nullptr)
255             {
256                 std::string_view selectedProtocol(
257                     std::bit_cast<const char*>(alpn), alpnlen);
258                 BMCWEB_LOG_DEBUG("ALPN selected protocol \"{}\" len: {}",
259                                  selectedProtocol, alpnlen);
260                 if (selectedProtocol == "h2")
261                 {
262                     upgradeToHttp2();
263                     return;
264                 }
265             }
266         }
267 
268         doReadHeaders();
269     }
270 
initParser()271     void initParser()
272     {
273         boost::beast::http::request_parser<bmcweb::HttpBody>& instance =
274             parser.emplace();
275 
276         // reset header limit for newly created parser
277         instance.header_limit(httpHeaderLimit);
278 
279         // Initially set no body limit. We don't yet know if the user is
280         // authenticated.
281         instance.body_limit(boost::none);
282     }
283 
upgradeToHttp2()284     void upgradeToHttp2()
285     {
286         auto http2 = std::make_shared<HTTP2Connection<Adaptor, Handler>>(
287             std::move(adaptor), handler, getCachedDateStr, httpType);
288         if (http2settings.empty())
289         {
290             http2->start();
291         }
292         else
293         {
294             http2->startFromSettings(http2settings);
295         }
296     }
297 
298     // returns whether connection was upgraded
doUpgrade(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)299     bool doUpgrade(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
300     {
301         using boost::beast::http::field;
302         using boost::beast::http::token_list;
303 
304         bool isSse =
305             isContentTypeAllowed(req->getHeaderValue("Accept"),
306                                  http_helpers::ContentType::EventStream, false);
307 
308         bool isWebsocket = false;
309         bool isH2c = false;
310         // Check connection header is upgrade
311         if (token_list{req->req[field::connection]}.exists("upgrade"))
312         {
313             BMCWEB_LOG_DEBUG("{} Connection: Upgrade header was present",
314                              logPtr(this));
315             // Parse if upgrade is h2c or websocket
316             token_list upgrade{req->req[field::upgrade]};
317             isWebsocket = upgrade.exists("websocket");
318             isH2c = upgrade.exists("h2c");
319             BMCWEB_LOG_DEBUG("{} Upgrade isWebsocket: {} isH2c: {}",
320                              logPtr(this), isWebsocket, isH2c);
321         }
322 
323         if (BMCWEB_EXPERIMENTAL_HTTP2 && isH2c)
324         {
325             std::string_view base64settings = req->req[field::http2_settings];
326             if (utility::base64Decode<true>(base64settings, http2settings))
327             {
328                 res.result(boost::beast::http::status::switching_protocols);
329                 res.addHeader(boost::beast::http::field::connection, "Upgrade");
330                 res.addHeader(boost::beast::http::field::upgrade, "h2c");
331             }
332         }
333 
334         // websocket and SSE are only allowed on GET
335         if (req->req.method() == boost::beast::http::verb::get)
336         {
337             if (isWebsocket || isSse)
338             {
339                 asyncResp->res.setCompleteRequestHandler(
340                     [self(shared_from_this())](crow::Response& thisRes) {
341                         if (thisRes.result() != boost::beast::http::status::ok)
342                         {
343                             // When any error occurs before handle upgradation,
344                             // the result in response will be set to respective
345                             // error. By default the Result will be OK (200),
346                             // which implies successful handle upgrade. Response
347                             // needs to be sent over this connection only on
348                             // failure.
349                             self->completeRequest(thisRes);
350                             return;
351                         }
352                     });
353                 BMCWEB_LOG_INFO("{} Upgrading socket", logPtr(this));
354                 if (httpType == HttpType::HTTP)
355                 {
356                     handler->handleUpgrade(req, asyncResp,
357                                            std::move(adaptor.next_layer()));
358                 }
359                 else
360                 {
361                     handler->handleUpgrade(req, asyncResp, std::move(adaptor));
362                 }
363 
364                 return true;
365             }
366         }
367         return false;
368     }
369 
handle()370     void handle()
371     {
372         std::error_code reqEc;
373         if (!parser)
374         {
375             return;
376         }
377         req = std::make_shared<crow::Request>(parser->release(), reqEc);
378         if (reqEc)
379         {
380             BMCWEB_LOG_DEBUG("Request failed to construct{}", reqEc.message());
381             res.result(boost::beast::http::status::bad_request);
382             completeRequest(res);
383             return;
384         }
385         req->session = userSession;
386         accept = req->getHeaderValue("Accept");
387         // Fetch the client IP address
388         req->ipAddress = ip;
389 
390         // Check for HTTP version 1.1.
391         if (req->version() == 11)
392         {
393             if (req->getHeaderValue(boost::beast::http::field::host).empty())
394             {
395                 res.result(boost::beast::http::status::bad_request);
396                 completeRequest(res);
397                 return;
398             }
399         }
400 
401         BMCWEB_LOG_INFO("Request:  {} HTTP/{}.{} {} {} {}", logPtr(this),
402                         req->version() / 10, req->version() % 10,
403                         req->methodString(), req->target(),
404                         req->ipAddress.to_string());
405 
406         if (res.completed)
407         {
408             completeRequest(res);
409             return;
410         }
411         keepAlive = req->keepAlive();
412 
413         if (authenticationEnabled)
414         {
415             if (!crow::authentication::isOnAllowlist(req->url().path(),
416                                                      req->method()) &&
417                 req->session == nullptr)
418             {
419                 BMCWEB_LOG_WARNING("Authentication failed");
420                 forward_unauthorized::sendUnauthorized(
421                     req->url().encoded_path(),
422                     req->getHeaderValue("X-Requested-With"),
423                     req->getHeaderValue("Accept"), res);
424                 completeRequest(res);
425                 return;
426             }
427         }
428 
429         auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
430         BMCWEB_LOG_DEBUG("Setting completion handler");
431         asyncResp->res.setCompleteRequestHandler(
432             [self(shared_from_this())](crow::Response& thisRes) {
433                 self->completeRequest(thisRes);
434             });
435         if (doUpgrade(asyncResp))
436         {
437             return;
438         }
439         std::string_view expected =
440             req->getHeaderValue(boost::beast::http::field::if_none_match);
441         if (!expected.empty())
442         {
443             asyncResp->res.setExpectedHash(expected);
444         }
445         handler->handle(req, asyncResp);
446     }
447 
hardClose()448     void hardClose()
449     {
450         BMCWEB_LOG_DEBUG("{} Closing socket", logPtr(this));
451         adaptor.next_layer().close();
452     }
453 
tlsShutdownComplete(const std::shared_ptr<self_type> & self,const boost::system::error_code & ec)454     void tlsShutdownComplete(const std::shared_ptr<self_type>& self,
455                              const boost::system::error_code& ec)
456     {
457         if (ec)
458         {
459             BMCWEB_LOG_WARNING("{} Failed to shut down TLS cleanly {}",
460                                logPtr(self.get()), ec);
461         }
462         self->hardClose();
463     }
464 
gracefulClose()465     void gracefulClose()
466     {
467         BMCWEB_LOG_DEBUG("{} Socket close requested", logPtr(this));
468 
469         if (httpType == HttpType::HTTPS)
470         {
471             if (mtlsSession != nullptr)
472             {
473                 BMCWEB_LOG_DEBUG("{} Removing TLS session: {}", logPtr(this),
474                                  mtlsSession->uniqueId);
475                 persistent_data::SessionStore::getInstance().removeSession(
476                     mtlsSession);
477             }
478 
479             adaptor.async_shutdown(std::bind_front(
480                 &self_type::tlsShutdownComplete, this, shared_from_this()));
481         }
482         else
483         {
484             hardClose();
485         }
486     }
487 
completeRequest(crow::Response & thisRes)488     void completeRequest(crow::Response& thisRes)
489     {
490         res = std::move(thisRes);
491         res.keepAlive(keepAlive);
492 
493         completeResponseFields(accept, res);
494         res.addHeader(boost::beast::http::field::date, getCachedDateStr());
495 
496         doWrite();
497 
498         // delete lambda with self shared_ptr
499         // to enable connection destruction
500         res.setCompleteRequestHandler(nullptr);
501     }
502 
readClientIp()503     void readClientIp()
504     {
505         boost::system::error_code ec;
506 
507         boost::asio::ip::tcp::endpoint endpoint =
508             boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
509 
510         if (ec)
511         {
512             // If remote endpoint fails keep going. "ClientOriginIPAddress"
513             // will be empty.
514             BMCWEB_LOG_ERROR("Failed to get the client's IP Address. ec : {}",
515                              ec);
516             return;
517         }
518         ip = endpoint.address();
519     }
520 
disableAuth()521     void disableAuth()
522     {
523         authenticationEnabled = false;
524     }
525 
526   private:
getContentLengthLimit()527     uint64_t getContentLengthLimit()
528     {
529         if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
530         {
531             if (userSession == nullptr)
532             {
533                 return loggedOutPostBodyLimit;
534             }
535         }
536 
537         return httpReqBodyLimit;
538     }
539 
540     // Returns true if content length was within limits
541     // Returns false if content length error has been returned
handleContentLengthError()542     bool handleContentLengthError()
543     {
544         if (!parser)
545         {
546             BMCWEB_LOG_CRITICAL("Parser was null");
547             return false;
548         }
549         const boost::optional<uint64_t> contentLength =
550             parser->content_length();
551         if (!contentLength)
552         {
553             BMCWEB_LOG_DEBUG("{} No content length available", logPtr(this));
554             return true;
555         }
556 
557         uint64_t maxAllowedContentLength = getContentLengthLimit();
558 
559         if (*contentLength > maxAllowedContentLength)
560         {
561             // If the users content limit is between the logged in
562             // and logged out limits They probably just didn't log
563             // in
564             if (*contentLength > loggedOutPostBodyLimit &&
565                 *contentLength < httpReqBodyLimit)
566             {
567                 BMCWEB_LOG_DEBUG(
568                     "{} Content length {} valid, but greater than logged out"
569                     " limit of {}. Setting unauthorized",
570                     logPtr(this), *contentLength, loggedOutPostBodyLimit);
571                 res.result(boost::beast::http::status::unauthorized);
572             }
573             else
574             {
575                 // Otherwise they're over both limits, so inform
576                 // them
577                 BMCWEB_LOG_DEBUG(
578                     "{} Content length {} was greater than global limit {}."
579                     " Setting payload too large",
580                     logPtr(this), *contentLength, httpReqBodyLimit);
581                 res.result(boost::beast::http::status::payload_too_large);
582             }
583 
584             keepAlive = false;
585             doWrite();
586             return false;
587         }
588 
589         return true;
590     }
591 
afterReadHeaders(const std::shared_ptr<self_type> &,const boost::system::error_code & ec,std::size_t bytesTransferred)592     void afterReadHeaders(const std::shared_ptr<self_type>& /*self*/,
593                           const boost::system::error_code& ec,
594                           std::size_t bytesTransferred)
595     {
596         BMCWEB_LOG_DEBUG("{} async_read_header {} Bytes", logPtr(this),
597                          bytesTransferred);
598 
599         if (ec)
600         {
601             cancelDeadlineTimer();
602 
603             if (ec == boost::beast::http::error::header_limit)
604             {
605                 BMCWEB_LOG_ERROR("{} Header field too large, closing",
606                                  logPtr(this), ec.message());
607 
608                 res.result(boost::beast::http::status::
609                                request_header_fields_too_large);
610                 keepAlive = false;
611                 doWrite();
612                 return;
613             }
614             BMCWEB_LOG_WARNING("{} End of stream, closing {}", logPtr(this),
615                                ec);
616             hardClose();
617             return;
618         }
619 
620         if (!parser)
621         {
622             BMCWEB_LOG_ERROR("Parser was unexpectedly null");
623             return;
624         }
625         auto& parse = *parser;
626         const auto& value = parser->get();
627 
628         if (authenticationEnabled)
629         {
630             boost::beast::http::verb method = value.method();
631             userSession = authentication::authenticate(
632                 ip, res, method, value.base(), mtlsSession);
633         }
634 
635         std::string_view expect = value[boost::beast::http::field::expect];
636         if (bmcweb::asciiIEquals(expect, "100-continue"))
637         {
638             res.result(boost::beast::http::status::continue_);
639             doWrite();
640             return;
641         }
642 
643         if (!handleContentLengthError())
644         {
645             return;
646         }
647 
648         parse.body_limit(getContentLengthLimit());
649 
650         if (parse.is_done())
651         {
652             handle();
653             return;
654         }
655 
656         doRead();
657     }
658 
doReadHeaders()659     void doReadHeaders()
660     {
661         BMCWEB_LOG_DEBUG("{} doReadHeaders", logPtr(this));
662         if (!parser)
663         {
664             BMCWEB_LOG_CRITICAL("Parser was not initialized.");
665             return;
666         }
667 
668         if (httpType == HttpType::HTTP)
669         {
670             boost::beast::http::async_read_header(
671                 adaptor.next_layer(), buffer, *parser,
672                 std::bind_front(&self_type::afterReadHeaders, this,
673                                 shared_from_this()));
674         }
675         else
676         {
677             boost::beast::http::async_read_header(
678                 adaptor, buffer, *parser,
679                 std::bind_front(&self_type::afterReadHeaders, this,
680                                 shared_from_this()));
681         }
682     }
683 
afterRead(const std::shared_ptr<self_type> &,const boost::system::error_code & ec,std::size_t bytesTransferred)684     void afterRead(const std::shared_ptr<self_type>& /*self*/,
685                    const boost::system::error_code& ec,
686                    std::size_t bytesTransferred)
687     {
688         BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
689                          bytesTransferred);
690 
691         if (ec)
692         {
693             BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
694                              ec.message());
695             if (ec == boost::beast::http::error::body_limit)
696             {
697                 if (handleContentLengthError())
698                 {
699                     BMCWEB_LOG_CRITICAL("Body length limit reached, "
700                                         "but no content-length "
701                                         "available?  Should never happen");
702                     res.result(
703                         boost::beast::http::status::internal_server_error);
704                     keepAlive = false;
705                     doWrite();
706                 }
707                 return;
708             }
709             BMCWEB_LOG_WARNING("{} End of stream, closing {}", logPtr(this),
710                                ec);
711             hardClose();
712 
713             return;
714         }
715 
716         // If the user is logged in, allow them to send files
717         // incrementally one piece at a time. If authentication is
718         // disabled then there is no user session hence always allow to
719         // send one piece at a time.
720         if (userSession != nullptr)
721         {
722             cancelDeadlineTimer();
723         }
724 
725         if (!parser)
726         {
727             BMCWEB_LOG_ERROR("Parser was unexpectedly null");
728             return;
729         }
730         if (!parser->is_done())
731         {
732             doRead();
733             return;
734         }
735 
736         cancelDeadlineTimer();
737         handle();
738     }
739 
doRead()740     void doRead()
741     {
742         BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
743         if (!parser)
744         {
745             return;
746         }
747         auto& parse = *parser;
748         startDeadline();
749         if (httpType == HttpType::HTTP)
750         {
751             boost::beast::http::async_read_some(
752                 adaptor.next_layer(), buffer, parse,
753                 std::bind_front(&self_type::afterRead, this,
754                                 shared_from_this()));
755         }
756         else
757         {
758             boost::beast::http::async_read_some(
759                 adaptor, buffer, parse,
760                 std::bind_front(&self_type::afterRead, this,
761                                 shared_from_this()));
762         }
763     }
764 
afterDoWrite(const std::shared_ptr<self_type> &,const boost::system::error_code & ec,std::size_t bytesTransferred)765     void afterDoWrite(const std::shared_ptr<self_type>& /*self*/,
766                       const boost::system::error_code& ec,
767                       std::size_t bytesTransferred)
768     {
769         BMCWEB_LOG_DEBUG("{} async_write wrote {} bytes, ec={}", logPtr(this),
770                          bytesTransferred, ec);
771 
772         cancelDeadlineTimer();
773 
774         if (ec == boost::system::errc::operation_would_block ||
775             ec == boost::system::errc::resource_unavailable_try_again)
776         {
777             doWrite();
778             return;
779         }
780 
781         if (ec == boost::beast::http::error::end_of_stream ||
782             ec == boost::asio::ssl::error::stream_truncated)
783         {
784             BMCWEB_LOG_WARNING("{} End of stream, closing {}", logPtr(this),
785                                ec);
786             hardClose();
787             return;
788         }
789 
790         if (ec)
791         {
792             BMCWEB_LOG_DEBUG("{} from write(2)", logPtr(this));
793             return;
794         }
795 
796         if (res.result() == boost::beast::http::status::switching_protocols)
797         {
798             upgradeToHttp2();
799             return;
800         }
801 
802         if (res.result() == boost::beast::http::status::continue_)
803         {
804             // Reset the result to ok
805             res.result(boost::beast::http::status::ok);
806             doRead();
807             return;
808         }
809 
810         if (!keepAlive)
811         {
812             BMCWEB_LOG_DEBUG("{} keepalive not set.  Closing socket",
813                              logPtr(this));
814 
815             gracefulClose();
816             return;
817         }
818 
819         BMCWEB_LOG_DEBUG("{} Clearing response", logPtr(this));
820         res.clear();
821         initParser();
822 
823         userSession = nullptr;
824 
825         req->clear();
826         doReadHeaders();
827     }
828 
doWrite()829     void doWrite()
830     {
831         BMCWEB_LOG_DEBUG("{} doWrite", logPtr(this));
832         res.preparePayload();
833 
834         startDeadline();
835         if (httpType == HttpType::HTTP)
836         {
837             boost::beast::async_write(
838                 adaptor.next_layer(),
839                 boost::beast::http::message_generator(std::move(res.response)),
840                 std::bind_front(&self_type::afterDoWrite, this,
841                                 shared_from_this()));
842         }
843         else
844         {
845             boost::beast::async_write(
846                 adaptor,
847                 boost::beast::http::message_generator(std::move(res.response)),
848                 std::bind_front(&self_type::afterDoWrite, this,
849                                 shared_from_this()));
850         }
851     }
852 
cancelDeadlineTimer()853     void cancelDeadlineTimer()
854     {
855         timer.cancel();
856     }
857 
afterTimerWait(const std::weak_ptr<self_type> & weakSelf,const boost::system::error_code & ec)858     void afterTimerWait(const std::weak_ptr<self_type>& weakSelf,
859                         const boost::system::error_code& ec)
860     {
861         // Note, we are ignoring other types of errors here;  If the timer
862         // failed for any reason, we should still close the connection
863         std::shared_ptr<Connection<Adaptor, Handler>> self = weakSelf.lock();
864         if (!self)
865         {
866             if (ec == boost::asio::error::operation_aborted)
867             {
868                 BMCWEB_LOG_DEBUG(
869                     "{} Timer canceled on connection being destroyed",
870                     logPtr(self.get()));
871             }
872             else
873             {
874                 BMCWEB_LOG_CRITICAL("{} Failed to capture connection",
875                                     logPtr(self.get()));
876             }
877             return;
878         }
879 
880         self->timerStarted = false;
881 
882         if (ec)
883         {
884             if (ec == boost::asio::error::operation_aborted)
885             {
886                 BMCWEB_LOG_DEBUG("{} Timer canceled", logPtr(self.get()));
887                 return;
888             }
889             BMCWEB_LOG_CRITICAL("{} Timer failed {}", logPtr(self.get()), ec);
890         }
891 
892         BMCWEB_LOG_WARNING("{} Connection timed out, hard closing",
893                            logPtr(self.get()));
894 
895         self->hardClose();
896     }
897 
startDeadline()898     void startDeadline()
899     {
900         // Timer is already started so no further action is required.
901         if (timerStarted)
902         {
903             return;
904         }
905 
906         std::chrono::seconds timeout(15);
907 
908         std::weak_ptr<Connection<Adaptor, Handler>> weakSelf = weak_from_this();
909         timer.expires_after(timeout);
910         timer.async_wait(std::bind_front(&self_type::afterTimerWait, this,
911                                          weak_from_this()));
912 
913         timerStarted = true;
914         BMCWEB_LOG_DEBUG("{} timer started", logPtr(this));
915     }
916 
917     bool authenticationEnabled = !BMCWEB_INSECURE_DISABLE_AUTH;
918     HttpType httpType = HttpType::BOTH;
919 
920     boost::asio::ssl::stream<Adaptor> adaptor;
921     Handler* handler;
922 
923     boost::asio::ip::address ip;
924 
925     // Making this a std::optional allows it to be efficiently destroyed and
926     // re-created on Connection reset
927     std::optional<boost::beast::http::request_parser<bmcweb::HttpBody>> parser;
928 
929     boost::beast::flat_static_buffer<8192> buffer;
930 
931     std::shared_ptr<crow::Request> req;
932     std::string accept;
933     std::string http2settings;
934     crow::Response res;
935 
936     std::shared_ptr<persistent_data::UserSession> userSession;
937     std::shared_ptr<persistent_data::UserSession> mtlsSession;
938 
939     boost::asio::steady_timer timer;
940 
941     bool keepAlive = true;
942 
943     bool timerStarted = false;
944 
945     std::function<std::string()>& getCachedDateStr;
946 
947     using std::enable_shared_from_this<
948         Connection<Adaptor, Handler>>::shared_from_this;
949 
950     using std::enable_shared_from_this<
951         Connection<Adaptor, Handler>>::weak_from_this;
952 };
953 } // namespace crow
954