xref: /openbmc/bmcweb/http/websocket.hpp (revision e05aec50f10116e6dda7e377bc61799aa5b7c166)
104e438cbSEd Tanous #pragma once
204e438cbSEd Tanous #include "http_request.hpp"
304e438cbSEd Tanous 
404e438cbSEd Tanous #include <async_resp.hpp>
504e438cbSEd Tanous #include <boost/algorithm/string/predicate.hpp>
604e438cbSEd Tanous #include <boost/asio/buffer.hpp>
704e438cbSEd Tanous #include <boost/beast/websocket.hpp>
804e438cbSEd Tanous 
904e438cbSEd Tanous #include <array>
1004e438cbSEd Tanous #include <functional>
1104e438cbSEd Tanous 
1204e438cbSEd Tanous #ifdef BMCWEB_ENABLE_SSL
1304e438cbSEd Tanous #include <boost/beast/websocket/ssl.hpp>
1404e438cbSEd Tanous #endif
1504e438cbSEd Tanous 
1604e438cbSEd Tanous namespace crow
1704e438cbSEd Tanous {
1804e438cbSEd Tanous namespace websocket
1904e438cbSEd Tanous {
2004e438cbSEd Tanous 
2104e438cbSEd Tanous struct Connection : std::enable_shared_from_this<Connection>
2204e438cbSEd Tanous {
2304e438cbSEd Tanous   public:
2404e438cbSEd Tanous     explicit Connection(const crow::Request& reqIn) :
2504e438cbSEd Tanous         req(reqIn.req), userdataPtr(nullptr)
2604e438cbSEd Tanous     {}
2704e438cbSEd Tanous 
2804e438cbSEd Tanous     explicit Connection(const crow::Request& reqIn, std::string user) :
2904e438cbSEd Tanous         req(reqIn.req), userName{std::move(user)}, userdataPtr(nullptr)
3004e438cbSEd Tanous     {}
3104e438cbSEd Tanous 
32ecd6a3a2SEd Tanous     Connection(const Connection&) = delete;
33ecd6a3a2SEd Tanous     Connection(Connection&&) = delete;
34ecd6a3a2SEd Tanous     Connection& operator=(const Connection&) = delete;
35ecd6a3a2SEd Tanous     Connection& operator=(const Connection&&) = delete;
36ecd6a3a2SEd Tanous 
379eb808c1SEd Tanous     virtual void sendBinary(std::string_view msg) = 0;
3804e438cbSEd Tanous     virtual void sendBinary(std::string&& msg) = 0;
399eb808c1SEd Tanous     virtual void sendText(std::string_view msg) = 0;
4004e438cbSEd Tanous     virtual void sendText(std::string&& msg) = 0;
419eb808c1SEd Tanous     virtual void close(std::string_view msg = "quit") = 0;
4204e438cbSEd Tanous     virtual boost::asio::io_context& getIoContext() = 0;
4304e438cbSEd Tanous     virtual ~Connection() = default;
4404e438cbSEd Tanous 
4504e438cbSEd Tanous     void userdata(void* u)
4604e438cbSEd Tanous     {
4704e438cbSEd Tanous         userdataPtr = u;
4804e438cbSEd Tanous     }
4904e438cbSEd Tanous     void* userdata()
5004e438cbSEd Tanous     {
5104e438cbSEd Tanous         return userdataPtr;
5204e438cbSEd Tanous     }
5304e438cbSEd Tanous 
5404e438cbSEd Tanous     const std::string& getUserName() const
5504e438cbSEd Tanous     {
5604e438cbSEd Tanous         return userName;
5704e438cbSEd Tanous     }
5804e438cbSEd Tanous 
5904e438cbSEd Tanous     boost::beast::http::request<boost::beast::http::string_body> req;
6004e438cbSEd Tanous     crow::Response res;
6104e438cbSEd Tanous 
6204e438cbSEd Tanous   private:
6304e438cbSEd Tanous     std::string userName{};
6404e438cbSEd Tanous     void* userdataPtr;
6504e438cbSEd Tanous };
6604e438cbSEd Tanous 
6704e438cbSEd Tanous template <typename Adaptor>
6804e438cbSEd Tanous class ConnectionImpl : public Connection
6904e438cbSEd Tanous {
7004e438cbSEd Tanous   public:
7104e438cbSEd Tanous     ConnectionImpl(
7204e438cbSEd Tanous         const crow::Request& reqIn, Adaptor adaptorIn,
73ccd584f2SGunnar Mills         std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
74ccd584f2SGunnar Mills             openHandler,
7504e438cbSEd Tanous         std::function<void(Connection&, const std::string&, bool)>
7681ce609eSEd Tanous             messageHandler,
7781ce609eSEd Tanous         std::function<void(Connection&, const std::string&)> closeHandler,
7881ce609eSEd Tanous         std::function<void(Connection&)> errorHandler) :
7904e438cbSEd Tanous         Connection(reqIn, reqIn.session->username),
80*e05aec50SEd Tanous         ws(std::move(adaptorIn)), inBuffer(inString, 131088),
8181ce609eSEd Tanous         openHandler(std::move(openHandler)),
8281ce609eSEd Tanous         messageHandler(std::move(messageHandler)),
8381ce609eSEd Tanous         closeHandler(std::move(closeHandler)),
8481ce609eSEd Tanous         errorHandler(std::move(errorHandler)), session(reqIn.session)
8504e438cbSEd Tanous     {
8602bdd967Sdhineskumare         /* Turn on the timeouts on websocket stream to server role */
8702bdd967Sdhineskumare         ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
8802bdd967Sdhineskumare             boost::beast::role_type::server));
8904e438cbSEd Tanous         BMCWEB_LOG_DEBUG << "Creating new connection " << this;
9004e438cbSEd Tanous     }
9104e438cbSEd Tanous 
9204e438cbSEd Tanous     boost::asio::io_context& getIoContext() override
9304e438cbSEd Tanous     {
9404e438cbSEd Tanous         return static_cast<boost::asio::io_context&>(
9504e438cbSEd Tanous             ws.get_executor().context());
9604e438cbSEd Tanous     }
9704e438cbSEd Tanous 
9804e438cbSEd Tanous     void start()
9904e438cbSEd Tanous     {
10004e438cbSEd Tanous         BMCWEB_LOG_DEBUG << "starting connection " << this;
10104e438cbSEd Tanous 
10204e438cbSEd Tanous         using bf = boost::beast::http::field;
10304e438cbSEd Tanous 
10404e438cbSEd Tanous         std::string_view protocol = req[bf::sec_websocket_protocol];
10504e438cbSEd Tanous 
10604e438cbSEd Tanous         ws.set_option(boost::beast::websocket::stream_base::decorator(
10704e438cbSEd Tanous             [session{session}, protocol{std::string(protocol)}](
10804e438cbSEd Tanous                 boost::beast::websocket::response_type& m) {
10904e438cbSEd Tanous 
11004e438cbSEd Tanous #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
111a90daf18SEd Tanous                 if (session != nullptr)
112a90daf18SEd Tanous                 {
11304e438cbSEd Tanous                     // use protocol for csrf checking
11404e438cbSEd Tanous                     if (session->cookieAuth &&
11504e438cbSEd Tanous                         !crow::utility::constantTimeStringCompare(
11604e438cbSEd Tanous                             protocol, session->csrfToken))
11704e438cbSEd Tanous                     {
11804e438cbSEd Tanous                         BMCWEB_LOG_ERROR << "Websocket CSRF error";
11904e438cbSEd Tanous                         m.result(boost::beast::http::status::unauthorized);
12004e438cbSEd Tanous                         return;
12104e438cbSEd Tanous                     }
122a90daf18SEd Tanous                 }
12304e438cbSEd Tanous #endif
12404e438cbSEd Tanous                 if (!protocol.empty())
12504e438cbSEd Tanous                 {
12604e438cbSEd Tanous                     m.insert(bf::sec_websocket_protocol, protocol);
12704e438cbSEd Tanous                 }
12804e438cbSEd Tanous 
12904e438cbSEd Tanous                 m.insert(bf::strict_transport_security, "max-age=31536000; "
13004e438cbSEd Tanous                                                         "includeSubdomains; "
13104e438cbSEd Tanous                                                         "preload");
13204e438cbSEd Tanous                 m.insert(bf::pragma, "no-cache");
13304e438cbSEd Tanous                 m.insert(bf::cache_control, "no-Store,no-Cache");
13404e438cbSEd Tanous                 m.insert("Content-Security-Policy", "default-src 'self'");
13504e438cbSEd Tanous                 m.insert("X-XSS-Protection", "1; "
13604e438cbSEd Tanous                                              "mode=block");
13704e438cbSEd Tanous                 m.insert("X-Content-Type-Options", "nosniff");
13804e438cbSEd Tanous             }));
13904e438cbSEd Tanous 
14004e438cbSEd Tanous         // Perform the websocket upgrade
14104e438cbSEd Tanous         ws.async_accept(req, [this, self(shared_from_this())](
14204e438cbSEd Tanous                                  boost::system::error_code ec) {
14304e438cbSEd Tanous             if (ec)
14404e438cbSEd Tanous             {
14504e438cbSEd Tanous                 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
14604e438cbSEd Tanous                 return;
14704e438cbSEd Tanous             }
14804e438cbSEd Tanous             acceptDone();
14904e438cbSEd Tanous         });
15004e438cbSEd Tanous     }
15104e438cbSEd Tanous 
15204e438cbSEd Tanous     void sendBinary(const std::string_view msg) override
15304e438cbSEd Tanous     {
15404e438cbSEd Tanous         ws.binary(true);
15504e438cbSEd Tanous         outBuffer.emplace_back(msg);
15604e438cbSEd Tanous         doWrite();
15704e438cbSEd Tanous     }
15804e438cbSEd Tanous 
15904e438cbSEd Tanous     void sendBinary(std::string&& msg) override
16004e438cbSEd Tanous     {
16104e438cbSEd Tanous         ws.binary(true);
16204e438cbSEd Tanous         outBuffer.emplace_back(std::move(msg));
16304e438cbSEd Tanous         doWrite();
16404e438cbSEd Tanous     }
16504e438cbSEd Tanous 
16604e438cbSEd Tanous     void sendText(const std::string_view msg) override
16704e438cbSEd Tanous     {
16804e438cbSEd Tanous         ws.text(true);
16904e438cbSEd Tanous         outBuffer.emplace_back(msg);
17004e438cbSEd Tanous         doWrite();
17104e438cbSEd Tanous     }
17204e438cbSEd Tanous 
17304e438cbSEd Tanous     void sendText(std::string&& msg) override
17404e438cbSEd Tanous     {
17504e438cbSEd Tanous         ws.text(true);
17604e438cbSEd Tanous         outBuffer.emplace_back(std::move(msg));
17704e438cbSEd Tanous         doWrite();
17804e438cbSEd Tanous     }
17904e438cbSEd Tanous 
18004e438cbSEd Tanous     void close(const std::string_view msg) override
18104e438cbSEd Tanous     {
18204e438cbSEd Tanous         ws.async_close(
18304e438cbSEd Tanous             {boost::beast::websocket::close_code::normal, msg},
18404e438cbSEd Tanous             [self(shared_from_this())](boost::system::error_code ec) {
18504e438cbSEd Tanous                 if (ec == boost::asio::error::operation_aborted)
18604e438cbSEd Tanous                 {
18704e438cbSEd Tanous                     return;
18804e438cbSEd Tanous                 }
18904e438cbSEd Tanous                 if (ec)
19004e438cbSEd Tanous                 {
19104e438cbSEd Tanous                     BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
19204e438cbSEd Tanous                     return;
19304e438cbSEd Tanous                 }
19404e438cbSEd Tanous             });
19504e438cbSEd Tanous     }
19604e438cbSEd Tanous 
19704e438cbSEd Tanous     void acceptDone()
19804e438cbSEd Tanous     {
19904e438cbSEd Tanous         BMCWEB_LOG_DEBUG << "Websocket accepted connection";
20004e438cbSEd Tanous 
2019062d478SGunnar Mills         auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
2029062d478SGunnar Mills             res, [this, self(shared_from_this())]() { doRead(); });
203ccd584f2SGunnar Mills 
204ccd584f2SGunnar Mills         asyncResp->res.result(boost::beast::http::status::ok);
20504e438cbSEd Tanous 
20604e438cbSEd Tanous         if (openHandler)
20704e438cbSEd Tanous         {
208ccd584f2SGunnar Mills             openHandler(*this, asyncResp);
20904e438cbSEd Tanous         }
21004e438cbSEd Tanous     }
21104e438cbSEd Tanous 
21204e438cbSEd Tanous     void doRead()
21304e438cbSEd Tanous     {
21404e438cbSEd Tanous         ws.async_read(inBuffer,
21504e438cbSEd Tanous                       [this, self(shared_from_this())](
21681ce609eSEd Tanous                           boost::beast::error_code ec, std::size_t bytesRead) {
21704e438cbSEd Tanous                           if (ec)
21804e438cbSEd Tanous                           {
21904e438cbSEd Tanous                               if (ec != boost::beast::websocket::error::closed)
22004e438cbSEd Tanous                               {
22104e438cbSEd Tanous                                   BMCWEB_LOG_ERROR << "doRead error " << ec;
22204e438cbSEd Tanous                               }
22304e438cbSEd Tanous                               if (closeHandler)
22404e438cbSEd Tanous                               {
22504e438cbSEd Tanous                                   std::string_view reason = ws.reason().reason;
22604e438cbSEd Tanous                                   closeHandler(*this, std::string(reason));
22704e438cbSEd Tanous                               }
22804e438cbSEd Tanous                               return;
22904e438cbSEd Tanous                           }
23004e438cbSEd Tanous                           if (messageHandler)
23104e438cbSEd Tanous                           {
23204e438cbSEd Tanous                               messageHandler(*this, inString, ws.got_text());
23304e438cbSEd Tanous                           }
23481ce609eSEd Tanous                           inBuffer.consume(bytesRead);
23504e438cbSEd Tanous                           inString.clear();
23604e438cbSEd Tanous                           doRead();
23704e438cbSEd Tanous                       });
23804e438cbSEd Tanous     }
23904e438cbSEd Tanous 
24004e438cbSEd Tanous     void doWrite()
24104e438cbSEd Tanous     {
24204e438cbSEd Tanous         // If we're already doing a write, ignore the request, it will be picked
24304e438cbSEd Tanous         // up when the current write is complete
24404e438cbSEd Tanous         if (doingWrite)
24504e438cbSEd Tanous         {
24604e438cbSEd Tanous             return;
24704e438cbSEd Tanous         }
24804e438cbSEd Tanous 
24904e438cbSEd Tanous         if (outBuffer.empty())
25004e438cbSEd Tanous         {
25104e438cbSEd Tanous             // Done for now
25204e438cbSEd Tanous             return;
25304e438cbSEd Tanous         }
25404e438cbSEd Tanous         doingWrite = true;
25504e438cbSEd Tanous         ws.async_write(boost::asio::buffer(outBuffer.front()),
25604e438cbSEd Tanous                        [this, self(shared_from_this())](
25704e438cbSEd Tanous                            boost::beast::error_code ec, std::size_t) {
25804e438cbSEd Tanous                            doingWrite = false;
25904e438cbSEd Tanous                            outBuffer.erase(outBuffer.begin());
26004e438cbSEd Tanous                            if (ec == boost::beast::websocket::error::closed)
26104e438cbSEd Tanous                            {
26204e438cbSEd Tanous                                // Do nothing here.  doRead handler will call the
26304e438cbSEd Tanous                                // closeHandler.
26404e438cbSEd Tanous                                close("Write error");
26504e438cbSEd Tanous                                return;
26604e438cbSEd Tanous                            }
26704e438cbSEd Tanous                            if (ec)
26804e438cbSEd Tanous                            {
26904e438cbSEd Tanous                                BMCWEB_LOG_ERROR << "Error in ws.async_write "
27004e438cbSEd Tanous                                                 << ec;
27104e438cbSEd Tanous                                return;
27204e438cbSEd Tanous                            }
27304e438cbSEd Tanous                            doWrite();
27404e438cbSEd Tanous                        });
27504e438cbSEd Tanous     }
27604e438cbSEd Tanous 
27704e438cbSEd Tanous   private:
2782aee6ca2SEd Tanous     boost::beast::websocket::stream<Adaptor, false> ws;
27904e438cbSEd Tanous 
28004e438cbSEd Tanous     std::string inString;
28104e438cbSEd Tanous     boost::asio::dynamic_string_buffer<std::string::value_type,
28204e438cbSEd Tanous                                        std::string::traits_type,
28304e438cbSEd Tanous                                        std::string::allocator_type>
28404e438cbSEd Tanous         inBuffer;
28504e438cbSEd Tanous     std::vector<std::string> outBuffer;
28604e438cbSEd Tanous     bool doingWrite = false;
28704e438cbSEd Tanous 
288ccd584f2SGunnar Mills     std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
289ccd584f2SGunnar Mills         openHandler;
29004e438cbSEd Tanous     std::function<void(Connection&, const std::string&, bool)> messageHandler;
29104e438cbSEd Tanous     std::function<void(Connection&, const std::string&)> closeHandler;
29204e438cbSEd Tanous     std::function<void(Connection&)> errorHandler;
29304e438cbSEd Tanous     std::shared_ptr<persistent_data::UserSession> session;
29404e438cbSEd Tanous };
29504e438cbSEd Tanous } // namespace websocket
29604e438cbSEd Tanous } // namespace crow
297