xref: /openbmc/bmcweb/http/websocket.hpp (revision 04e438cbad66838724d78ce12f28aff1fb892a63)
1*04e438cbSEd Tanous #pragma once
2*04e438cbSEd Tanous #include "http_request.hpp"
3*04e438cbSEd Tanous 
4*04e438cbSEd Tanous #include <async_resp.hpp>
5*04e438cbSEd Tanous #include <boost/algorithm/string/predicate.hpp>
6*04e438cbSEd Tanous #include <boost/asio/buffer.hpp>
7*04e438cbSEd Tanous #include <boost/beast/websocket.hpp>
8*04e438cbSEd Tanous 
9*04e438cbSEd Tanous #include <array>
10*04e438cbSEd Tanous #include <functional>
11*04e438cbSEd Tanous 
12*04e438cbSEd Tanous #ifdef BMCWEB_ENABLE_SSL
13*04e438cbSEd Tanous #include <boost/beast/websocket/ssl.hpp>
14*04e438cbSEd Tanous #endif
15*04e438cbSEd Tanous 
16*04e438cbSEd Tanous namespace crow
17*04e438cbSEd Tanous {
18*04e438cbSEd Tanous namespace websocket
19*04e438cbSEd Tanous {
20*04e438cbSEd Tanous 
21*04e438cbSEd Tanous struct Connection : std::enable_shared_from_this<Connection>
22*04e438cbSEd Tanous {
23*04e438cbSEd Tanous   public:
24*04e438cbSEd Tanous     explicit Connection(const crow::Request& reqIn) :
25*04e438cbSEd Tanous         req(reqIn.req), userdataPtr(nullptr)
26*04e438cbSEd Tanous     {}
27*04e438cbSEd Tanous 
28*04e438cbSEd Tanous     explicit Connection(const crow::Request& reqIn, std::string user) :
29*04e438cbSEd Tanous         req(reqIn.req), userName{std::move(user)}, userdataPtr(nullptr)
30*04e438cbSEd Tanous     {}
31*04e438cbSEd Tanous 
32*04e438cbSEd Tanous     virtual void sendBinary(const std::string_view msg) = 0;
33*04e438cbSEd Tanous     virtual void sendBinary(std::string&& msg) = 0;
34*04e438cbSEd Tanous     virtual void sendText(const std::string_view msg) = 0;
35*04e438cbSEd Tanous     virtual void sendText(std::string&& msg) = 0;
36*04e438cbSEd Tanous     virtual void close(const std::string_view msg = "quit") = 0;
37*04e438cbSEd Tanous     virtual boost::asio::io_context& getIoContext() = 0;
38*04e438cbSEd Tanous     virtual ~Connection() = default;
39*04e438cbSEd Tanous 
40*04e438cbSEd Tanous     void userdata(void* u)
41*04e438cbSEd Tanous     {
42*04e438cbSEd Tanous         userdataPtr = u;
43*04e438cbSEd Tanous     }
44*04e438cbSEd Tanous     void* userdata()
45*04e438cbSEd Tanous     {
46*04e438cbSEd Tanous         return userdataPtr;
47*04e438cbSEd Tanous     }
48*04e438cbSEd Tanous 
49*04e438cbSEd Tanous     const std::string& getUserName() const
50*04e438cbSEd Tanous     {
51*04e438cbSEd Tanous         return userName;
52*04e438cbSEd Tanous     }
53*04e438cbSEd Tanous 
54*04e438cbSEd Tanous     boost::beast::http::request<boost::beast::http::string_body> req;
55*04e438cbSEd Tanous     crow::Response res;
56*04e438cbSEd Tanous 
57*04e438cbSEd Tanous   private:
58*04e438cbSEd Tanous     std::string userName{};
59*04e438cbSEd Tanous     void* userdataPtr;
60*04e438cbSEd Tanous };
61*04e438cbSEd Tanous 
62*04e438cbSEd Tanous template <typename Adaptor>
63*04e438cbSEd Tanous class ConnectionImpl : public Connection
64*04e438cbSEd Tanous {
65*04e438cbSEd Tanous   public:
66*04e438cbSEd Tanous     ConnectionImpl(
67*04e438cbSEd Tanous         const crow::Request& reqIn, Adaptor adaptorIn,
68*04e438cbSEd Tanous         std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
69*04e438cbSEd Tanous             open_handler,
70*04e438cbSEd Tanous         std::function<void(Connection&, const std::string&, bool)>
71*04e438cbSEd Tanous             message_handler,
72*04e438cbSEd Tanous         std::function<void(Connection&, const std::string&)> close_handler,
73*04e438cbSEd Tanous         std::function<void(Connection&)> error_handler) :
74*04e438cbSEd Tanous         Connection(reqIn, reqIn.session->username),
75*04e438cbSEd Tanous         ws(std::move(adaptorIn)), inString(), inBuffer(inString, 131088),
76*04e438cbSEd Tanous         openHandler(std::move(open_handler)),
77*04e438cbSEd Tanous         messageHandler(std::move(message_handler)),
78*04e438cbSEd Tanous         closeHandler(std::move(close_handler)),
79*04e438cbSEd Tanous         errorHandler(std::move(error_handler)), session(reqIn.session)
80*04e438cbSEd Tanous     {
81*04e438cbSEd Tanous         BMCWEB_LOG_DEBUG << "Creating new connection " << this;
82*04e438cbSEd Tanous     }
83*04e438cbSEd Tanous 
84*04e438cbSEd Tanous     boost::asio::io_context& getIoContext() override
85*04e438cbSEd Tanous     {
86*04e438cbSEd Tanous         return static_cast<boost::asio::io_context&>(
87*04e438cbSEd Tanous             ws.get_executor().context());
88*04e438cbSEd Tanous     }
89*04e438cbSEd Tanous 
90*04e438cbSEd Tanous     void start()
91*04e438cbSEd Tanous     {
92*04e438cbSEd Tanous         BMCWEB_LOG_DEBUG << "starting connection " << this;
93*04e438cbSEd Tanous 
94*04e438cbSEd Tanous         using bf = boost::beast::http::field;
95*04e438cbSEd Tanous 
96*04e438cbSEd Tanous         std::string_view protocol = req[bf::sec_websocket_protocol];
97*04e438cbSEd Tanous 
98*04e438cbSEd Tanous         ws.set_option(boost::beast::websocket::stream_base::decorator(
99*04e438cbSEd Tanous             [session{session}, protocol{std::string(protocol)}](
100*04e438cbSEd Tanous                 boost::beast::websocket::response_type& m) {
101*04e438cbSEd Tanous 
102*04e438cbSEd Tanous #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION
103*04e438cbSEd Tanous                 // use protocol for csrf checking
104*04e438cbSEd Tanous                 if (session->cookieAuth &&
105*04e438cbSEd Tanous                     !crow::utility::constantTimeStringCompare(
106*04e438cbSEd Tanous                         protocol, session->csrfToken))
107*04e438cbSEd Tanous                 {
108*04e438cbSEd Tanous                     BMCWEB_LOG_ERROR << "Websocket CSRF error";
109*04e438cbSEd Tanous                     m.result(boost::beast::http::status::unauthorized);
110*04e438cbSEd Tanous                     return;
111*04e438cbSEd Tanous                 }
112*04e438cbSEd Tanous #endif
113*04e438cbSEd Tanous                 if (!protocol.empty())
114*04e438cbSEd Tanous                 {
115*04e438cbSEd Tanous                     m.insert(bf::sec_websocket_protocol, protocol);
116*04e438cbSEd Tanous                 }
117*04e438cbSEd Tanous 
118*04e438cbSEd Tanous                 m.insert(bf::strict_transport_security, "max-age=31536000; "
119*04e438cbSEd Tanous                                                         "includeSubdomains; "
120*04e438cbSEd Tanous                                                         "preload");
121*04e438cbSEd Tanous                 m.insert(bf::pragma, "no-cache");
122*04e438cbSEd Tanous                 m.insert(bf::cache_control, "no-Store,no-Cache");
123*04e438cbSEd Tanous                 m.insert("Content-Security-Policy", "default-src 'self'");
124*04e438cbSEd Tanous                 m.insert("X-XSS-Protection", "1; "
125*04e438cbSEd Tanous                                              "mode=block");
126*04e438cbSEd Tanous                 m.insert("X-Content-Type-Options", "nosniff");
127*04e438cbSEd Tanous             }));
128*04e438cbSEd Tanous 
129*04e438cbSEd Tanous         // Perform the websocket upgrade
130*04e438cbSEd Tanous         ws.async_accept(req, [this, self(shared_from_this())](
131*04e438cbSEd Tanous                                  boost::system::error_code ec) {
132*04e438cbSEd Tanous             if (ec)
133*04e438cbSEd Tanous             {
134*04e438cbSEd Tanous                 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
135*04e438cbSEd Tanous                 return;
136*04e438cbSEd Tanous             }
137*04e438cbSEd Tanous             acceptDone();
138*04e438cbSEd Tanous         });
139*04e438cbSEd Tanous     }
140*04e438cbSEd Tanous 
141*04e438cbSEd Tanous     void sendBinary(const std::string_view msg) override
142*04e438cbSEd Tanous     {
143*04e438cbSEd Tanous         ws.binary(true);
144*04e438cbSEd Tanous         outBuffer.emplace_back(msg);
145*04e438cbSEd Tanous         doWrite();
146*04e438cbSEd Tanous     }
147*04e438cbSEd Tanous 
148*04e438cbSEd Tanous     void sendBinary(std::string&& msg) override
149*04e438cbSEd Tanous     {
150*04e438cbSEd Tanous         ws.binary(true);
151*04e438cbSEd Tanous         outBuffer.emplace_back(std::move(msg));
152*04e438cbSEd Tanous         doWrite();
153*04e438cbSEd Tanous     }
154*04e438cbSEd Tanous 
155*04e438cbSEd Tanous     void sendText(const std::string_view msg) override
156*04e438cbSEd Tanous     {
157*04e438cbSEd Tanous         ws.text(true);
158*04e438cbSEd Tanous         outBuffer.emplace_back(msg);
159*04e438cbSEd Tanous         doWrite();
160*04e438cbSEd Tanous     }
161*04e438cbSEd Tanous 
162*04e438cbSEd Tanous     void sendText(std::string&& msg) override
163*04e438cbSEd Tanous     {
164*04e438cbSEd Tanous         ws.text(true);
165*04e438cbSEd Tanous         outBuffer.emplace_back(std::move(msg));
166*04e438cbSEd Tanous         doWrite();
167*04e438cbSEd Tanous     }
168*04e438cbSEd Tanous 
169*04e438cbSEd Tanous     void close(const std::string_view msg) override
170*04e438cbSEd Tanous     {
171*04e438cbSEd Tanous         ws.async_close(
172*04e438cbSEd Tanous             {boost::beast::websocket::close_code::normal, msg},
173*04e438cbSEd Tanous             [self(shared_from_this())](boost::system::error_code ec) {
174*04e438cbSEd Tanous                 if (ec == boost::asio::error::operation_aborted)
175*04e438cbSEd Tanous                 {
176*04e438cbSEd Tanous                     return;
177*04e438cbSEd Tanous                 }
178*04e438cbSEd Tanous                 if (ec)
179*04e438cbSEd Tanous                 {
180*04e438cbSEd Tanous                     BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
181*04e438cbSEd Tanous                     return;
182*04e438cbSEd Tanous                 }
183*04e438cbSEd Tanous             });
184*04e438cbSEd Tanous     }
185*04e438cbSEd Tanous 
186*04e438cbSEd Tanous     void acceptDone()
187*04e438cbSEd Tanous     {
188*04e438cbSEd Tanous         BMCWEB_LOG_DEBUG << "Websocket accepted connection";
189*04e438cbSEd Tanous 
190*04e438cbSEd Tanous         auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
191*04e438cbSEd Tanous             res, [this, self(shared_from_this())]() { doRead(); });
192*04e438cbSEd Tanous 
193*04e438cbSEd Tanous         asyncResp->res.result(boost::beast::http::status::ok);
194*04e438cbSEd Tanous 
195*04e438cbSEd Tanous         if (openHandler)
196*04e438cbSEd Tanous         {
197*04e438cbSEd Tanous             openHandler(*this, asyncResp);
198*04e438cbSEd Tanous         }
199*04e438cbSEd Tanous     }
200*04e438cbSEd Tanous 
201*04e438cbSEd Tanous     void doRead()
202*04e438cbSEd Tanous     {
203*04e438cbSEd Tanous         ws.async_read(inBuffer,
204*04e438cbSEd Tanous                       [this, self(shared_from_this())](
205*04e438cbSEd Tanous                           boost::beast::error_code ec, std::size_t bytes_read) {
206*04e438cbSEd Tanous                           if (ec)
207*04e438cbSEd Tanous                           {
208*04e438cbSEd Tanous                               if (ec != boost::beast::websocket::error::closed)
209*04e438cbSEd Tanous                               {
210*04e438cbSEd Tanous                                   BMCWEB_LOG_ERROR << "doRead error " << ec;
211*04e438cbSEd Tanous                               }
212*04e438cbSEd Tanous                               if (closeHandler)
213*04e438cbSEd Tanous                               {
214*04e438cbSEd Tanous                                   std::string_view reason = ws.reason().reason;
215*04e438cbSEd Tanous                                   closeHandler(*this, std::string(reason));
216*04e438cbSEd Tanous                               }
217*04e438cbSEd Tanous                               return;
218*04e438cbSEd Tanous                           }
219*04e438cbSEd Tanous                           if (messageHandler)
220*04e438cbSEd Tanous                           {
221*04e438cbSEd Tanous                               messageHandler(*this, inString, ws.got_text());
222*04e438cbSEd Tanous                           }
223*04e438cbSEd Tanous                           inBuffer.consume(bytes_read);
224*04e438cbSEd Tanous                           inString.clear();
225*04e438cbSEd Tanous                           doRead();
226*04e438cbSEd Tanous                       });
227*04e438cbSEd Tanous     }
228*04e438cbSEd Tanous 
229*04e438cbSEd Tanous     void doWrite()
230*04e438cbSEd Tanous     {
231*04e438cbSEd Tanous         // If we're already doing a write, ignore the request, it will be picked
232*04e438cbSEd Tanous         // up when the current write is complete
233*04e438cbSEd Tanous         if (doingWrite)
234*04e438cbSEd Tanous         {
235*04e438cbSEd Tanous             return;
236*04e438cbSEd Tanous         }
237*04e438cbSEd Tanous 
238*04e438cbSEd Tanous         if (outBuffer.empty())
239*04e438cbSEd Tanous         {
240*04e438cbSEd Tanous             // Done for now
241*04e438cbSEd Tanous             return;
242*04e438cbSEd Tanous         }
243*04e438cbSEd Tanous         doingWrite = true;
244*04e438cbSEd Tanous         ws.async_write(boost::asio::buffer(outBuffer.front()),
245*04e438cbSEd Tanous                        [this, self(shared_from_this())](
246*04e438cbSEd Tanous                            boost::beast::error_code ec, std::size_t) {
247*04e438cbSEd Tanous                            doingWrite = false;
248*04e438cbSEd Tanous                            outBuffer.erase(outBuffer.begin());
249*04e438cbSEd Tanous                            if (ec == boost::beast::websocket::error::closed)
250*04e438cbSEd Tanous                            {
251*04e438cbSEd Tanous                                // Do nothing here.  doRead handler will call the
252*04e438cbSEd Tanous                                // closeHandler.
253*04e438cbSEd Tanous                                close("Write error");
254*04e438cbSEd Tanous                                return;
255*04e438cbSEd Tanous                            }
256*04e438cbSEd Tanous                            if (ec)
257*04e438cbSEd Tanous                            {
258*04e438cbSEd Tanous                                BMCWEB_LOG_ERROR << "Error in ws.async_write "
259*04e438cbSEd Tanous                                                 << ec;
260*04e438cbSEd Tanous                                return;
261*04e438cbSEd Tanous                            }
262*04e438cbSEd Tanous                            doWrite();
263*04e438cbSEd Tanous                        });
264*04e438cbSEd Tanous     }
265*04e438cbSEd Tanous 
266*04e438cbSEd Tanous   private:
267*04e438cbSEd Tanous     boost::beast::websocket::stream<Adaptor> ws;
268*04e438cbSEd Tanous 
269*04e438cbSEd Tanous     std::string inString;
270*04e438cbSEd Tanous     boost::asio::dynamic_string_buffer<std::string::value_type,
271*04e438cbSEd Tanous                                        std::string::traits_type,
272*04e438cbSEd Tanous                                        std::string::allocator_type>
273*04e438cbSEd Tanous         inBuffer;
274*04e438cbSEd Tanous     std::vector<std::string> outBuffer;
275*04e438cbSEd Tanous     bool doingWrite = false;
276*04e438cbSEd Tanous 
277*04e438cbSEd Tanous     std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
278*04e438cbSEd Tanous         openHandler;
279*04e438cbSEd Tanous     std::function<void(Connection&, const std::string&, bool)> messageHandler;
280*04e438cbSEd Tanous     std::function<void(Connection&, const std::string&)> closeHandler;
281*04e438cbSEd Tanous     std::function<void(Connection&)> errorHandler;
282*04e438cbSEd Tanous     std::shared_ptr<persistent_data::UserSession> session;
283*04e438cbSEd Tanous };
284*04e438cbSEd Tanous } // namespace websocket
285*04e438cbSEd Tanous } // namespace crow
286