blob: 1f228909fcd4bdaa624c8ac6332549720076cdf9 [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Ed Tanous7045c8d2017-04-03 10:04:37 -07003#pragma once
Ed Tanousd7857202025-01-28 15:32:26 -08004#include "bmcweb_config.h"
5
6#include "boost_formatters.hpp"
Ed Tanousb2896142024-01-31 15:25:47 -08007#include "http_body.hpp"
Ed Tanous04e438c2020-10-03 08:06:26 -07008#include "http_request.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -08009#include "logging.hpp"
10#include "ossl_random.hpp"
11#include "sessions.hpp"
Gunnar Mills1214b7e2020-06-04 10:11:30 -050012
Ed Tanous609145a2018-09-05 16:27:36 -070013#include <boost/asio/buffer.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080014#include <boost/asio/error.hpp>
15#include <boost/asio/io_context.hpp>
Lei YUad6dd392024-09-12 11:07:23 +000016#include <boost/asio/ssl/error.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080017#include <boost/beast/core/error.hpp>
Ed Tanous863c1c22022-02-21 21:33:06 -080018#include <boost/beast/core/multi_buffer.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080019#include <boost/beast/core/role.hpp>
20#include <boost/beast/http/field.hpp>
21#include <boost/beast/http/message.hpp>
22#include <boost/beast/http/status.hpp>
23#include <boost/beast/websocket/error.hpp>
24#include <boost/beast/websocket/rfc6455.hpp>
25#include <boost/beast/websocket/stream.hpp>
26#include <boost/beast/websocket/stream_base.hpp>
27#include <boost/url/url_view.hpp>
28
29// NOLINTNEXTLINE(misc-include-cleaner)
Ed Tanous8db83742024-04-13 09:11:15 -070030#include <boost/beast/websocket/ssl.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -070031
Ed Tanousd7857202025-01-28 15:32:26 -080032#include <cstddef>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050033#include <functional>
Ed Tanousd7857202025-01-28 15:32:26 -080034#include <memory>
35#include <string>
36#include <string_view>
37#include <utility>
Ed Tanous1b0044b2018-08-03 14:30:05 -070038
Ed Tanous1abe55e2018-09-05 08:30:59 -070039namespace crow
40{
41namespace websocket
42{
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020043
Ed Tanous863c1c22022-02-21 21:33:06 -080044enum class MessageType
45{
46 Binary,
47 Text,
48};
49
Ed Tanous1abe55e2018-09-05 08:30:59 -070050struct Connection : std::enable_shared_from_this<Connection>
51{
52 public:
Ed Tanous5ebb9d32023-02-27 18:20:47 -080053 Connection() = default;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010054
Ed Tanousecd6a3a2022-01-07 09:18:40 -080055 Connection(const Connection&) = delete;
56 Connection(Connection&&) = delete;
57 Connection& operator=(const Connection&) = delete;
58 Connection& operator=(const Connection&&) = delete;
59
Ed Tanous9eb808c2022-01-25 10:19:23 -080060 virtual void sendBinary(std::string_view msg) = 0;
Ed Tanous863c1c22022-02-21 21:33:06 -080061 virtual void sendEx(MessageType type, std::string_view msg,
62 std::function<void()>&& onDone) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080063 virtual void sendText(std::string_view msg) = 0;
Ed Tanous9eb808c2022-01-25 10:19:23 -080064 virtual void close(std::string_view msg = "quit") = 0;
Ed Tanous863c1c22022-02-21 21:33:06 -080065 virtual void deferRead() = 0;
66 virtual void resumeRead() = 0;
Ed Tanous2c70f802020-09-28 14:29:23 -070067 virtual boost::asio::io_context& getIoContext() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070068 virtual ~Connection() = default;
Ninad Palsule052bcbf2023-05-30 11:10:58 -050069 virtual boost::urls::url_view url() = 0;
Ed Tanous7045c8d2017-04-03 10:04:37 -070070};
71
Gunnar Mills1214b7e2020-06-04 10:11:30 -050072template <typename Adaptor>
73class ConnectionImpl : public Connection
Ed Tanous1abe55e2018-09-05 08:30:59 -070074{
Ed Tanous5ebb9d32023-02-27 18:20:47 -080075 using self_t = ConnectionImpl<Adaptor>;
76
Ed Tanous1abe55e2018-09-05 08:30:59 -070077 public:
78 ConnectionImpl(
Ed Tanous5ebb9d32023-02-27 18:20:47 -080079 const boost::urls::url_view& urlViewIn,
80 const std::shared_ptr<persistent_data::UserSession>& sessionIn,
Ninad Palsule052bcbf2023-05-30 11:10:58 -050081 Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn,
Ed Tanous1abe55e2018-09-05 08:30:59 -070082 std::function<void(Connection&, const std::string&, bool)>
Ed Tanous8a592812022-06-04 09:06:59 -070083 messageHandlerIn,
Ed Tanous863c1c22022-02-21 21:33:06 -080084 std::function<void(crow::websocket::Connection&, std::string_view,
85 crow::websocket::MessageType type,
86 std::function<void()>&& whenComplete)>
87 messageExHandlerIn,
Ed Tanous8a592812022-06-04 09:06:59 -070088 std::function<void(Connection&, const std::string&)> closeHandlerIn,
89 std::function<void(Connection&)> errorHandlerIn) :
Patrick Williamsbd79bce2024-08-16 15:22:20 -040090 uri(urlViewIn), ws(std::move(adaptorIn)), inBuffer(inString, 131088),
Ed Tanous8a592812022-06-04 09:06:59 -070091 openHandler(std::move(openHandlerIn)),
92 messageHandler(std::move(messageHandlerIn)),
Ed Tanous863c1c22022-02-21 21:33:06 -080093 messageExHandler(std::move(messageExHandlerIn)),
Ed Tanous8a592812022-06-04 09:06:59 -070094 closeHandler(std::move(closeHandlerIn)),
Ed Tanous5ebb9d32023-02-27 18:20:47 -080095 errorHandler(std::move(errorHandlerIn)), session(sessionIn)
Ed Tanous1abe55e2018-09-05 08:30:59 -070096 {
dhineskumare02bdd962021-07-08 16:06:49 +053097 /* Turn on the timeouts on websocket stream to server role */
98 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested(
99 boost::beast::role_type::server));
Ed Tanous62598e32023-07-17 17:06:25 -0700100 BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -0700101 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700102
Ed Tanous2c70f802020-09-28 14:29:23 -0700103 boost::asio::io_context& getIoContext() override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700104 {
Ed Tanous271584a2019-07-09 16:24:22 -0700105 return static_cast<boost::asio::io_context&>(
106 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -0700107 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700108
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800109 void start(const crow::Request& req)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700110 {
Ed Tanous62598e32023-07-17 17:06:25 -0700111 BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this));
Ed Tanous7045c8d2017-04-03 10:04:37 -0700112
Ed Tanousfe5b2162019-05-22 14:28:16 -0700113 using bf = boost::beast::http::field;
Myung Bae1873a042024-04-01 09:27:39 -0500114 std::string protocolHeader{
115 req.getHeaderValue(bf::sec_websocket_protocol)};
Ed Tanous7045c8d2017-04-03 10:04:37 -0700116
Ed Tanousd4d77e32020-08-18 00:07:28 -0700117 ws.set_option(boost::beast::websocket::stream_base::decorator(
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800118 [session{session},
119 protocolHeader](boost::beast::websocket::response_type& m) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400120 if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF)
James Feistf8aa3d22020-04-08 18:32:33 -0700121 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400122 if (session != nullptr)
Ed Tanous83328312024-05-09 15:48:09 -0700123 {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400124 // use protocol for csrf checking
125 if (session->cookieAuth &&
126 !bmcweb::constantTimeStringCompare(
127 protocolHeader, session->csrfToken))
128 {
129 BMCWEB_LOG_ERROR("Websocket CSRF error");
130 m.result(boost::beast::http::status::unauthorized);
131 return;
132 }
Ed Tanous83328312024-05-09 15:48:09 -0700133 }
James Feistf8aa3d22020-04-08 18:32:33 -0700134 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400135 if (!protocolHeader.empty())
136 {
137 m.insert(bf::sec_websocket_protocol, protocolHeader);
138 }
Ed Tanousfe5b2162019-05-22 14:28:16 -0700139
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400140 m.insert(bf::strict_transport_security,
141 "max-age=31536000; "
142 "includeSubdomains; "
143 "preload");
144 m.insert(bf::pragma, "no-cache");
145 m.insert(bf::cache_control, "no-Store,no-Cache");
146 m.insert("Content-Security-Policy", "default-src 'self'");
147 m.insert("X-XSS-Protection", "1; "
148 "mode=block");
149 m.insert("X-Content-Type-Options", "nosniff");
150 }));
Ed Tanousd4d77e32020-08-18 00:07:28 -0700151
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800152 // Make a pointer to keep the req alive while we accept it.
Ed Tanousb2896142024-01-31 15:25:47 -0800153 using Body = boost::beast::http::request<bmcweb::HttpBody>;
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800154 std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req);
155 Body* ptr = mobile.get();
Ed Tanousd4d77e32020-08-18 00:07:28 -0700156 // Perform the websocket upgrade
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800157 ws.async_accept(*ptr,
158 std::bind_front(&self_t::acceptDone, this,
159 shared_from_this(), std::move(mobile)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700160 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700161
Ed Tanous26ccae32023-02-16 10:28:44 -0800162 void sendBinary(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700163 {
164 ws.binary(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800165 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
166 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700167 doWrite();
168 }
169
Ed Tanous863c1c22022-02-21 21:33:06 -0800170 void sendEx(MessageType type, std::string_view msg,
171 std::function<void()>&& onDone) override
172 {
173 if (doingWrite)
174 {
Ed Tanous62598e32023-07-17 17:06:25 -0700175 BMCWEB_LOG_CRITICAL(
176 "Cannot mix sendEx usage with sendBinary or sendText");
Ed Tanous863c1c22022-02-21 21:33:06 -0800177 onDone();
178 return;
179 }
180 ws.binary(type == MessageType::Binary);
181
182 ws.async_write(boost::asio::buffer(msg),
183 [weak(weak_from_this()), onDone{std::move(onDone)}](
184 const boost::beast::error_code& ec, size_t) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400185 std::shared_ptr<Connection> self = weak.lock();
186 if (!self)
187 {
188 BMCWEB_LOG_ERROR("Connection went away");
189 return;
190 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800191
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400192 // Call the done handler regardless of whether we
193 // errored, but before we close things out
194 onDone();
Ed Tanous863c1c22022-02-21 21:33:06 -0800195
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400196 if (ec)
197 {
198 BMCWEB_LOG_ERROR("Error in ws.async_write {}",
199 ec);
200 self->close("write error");
201 }
202 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800203 }
204
Ed Tanous26ccae32023-02-16 10:28:44 -0800205 void sendText(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700206 {
207 ws.text(true);
Ed Tanous863c1c22022-02-21 21:33:06 -0800208 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()),
209 boost::asio::buffer(msg)));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700210 doWrite();
211 }
212
Ed Tanous26ccae32023-02-16 10:28:44 -0800213 void close(std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700214 {
215 ws.async_close(
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200216 {boost::beast::websocket::close_code::normal, msg},
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800217 [self(shared_from_this())](const boost::system::error_code& ec) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400218 if (ec == boost::asio::error::operation_aborted)
219 {
220 return;
221 }
222 if (ec)
223 {
224 BMCWEB_LOG_ERROR("Error closing websocket {}", ec);
225 return;
226 }
227 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700228 }
229
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500230 boost::urls::url_view url() override
231 {
232 return uri;
233 }
234
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800235 void acceptDone(const std::shared_ptr<Connection>& /*self*/,
Ed Tanous52e31622024-01-23 16:31:11 -0800236 const std::unique_ptr<
Ed Tanousb2896142024-01-31 15:25:47 -0800237 boost::beast::http::request<bmcweb::HttpBody>>& /*req*/,
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800238 const boost::system::error_code& ec)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700239 {
Ed Tanous5ebb9d32023-02-27 18:20:47 -0800240 if (ec)
241 {
242 BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec);
243 return;
244 }
Ed Tanous62598e32023-07-17 17:06:25 -0700245 BMCWEB_LOG_DEBUG("Websocket accepted connection");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700246
247 if (openHandler)
248 {
zhanghch0577726382021-10-21 14:07:57 +0800249 openHandler(*this);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700250 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800251 doRead();
252 }
253
254 void deferRead() override
255 {
256 readingDefered = true;
257
258 // If we're not actively reading, we need to take ownership of
259 // ourselves for a small portion of time, do that, and clear when we
260 // resume.
261 selfOwned = shared_from_this();
262 }
263
264 void resumeRead() override
265 {
266 readingDefered = false;
267 doRead();
268
269 // No longer need to keep ourselves alive now that read is active.
270 selfOwned.reset();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700271 }
272
273 void doRead()
274 {
Ed Tanous863c1c22022-02-21 21:33:06 -0800275 if (readingDefered)
276 {
277 return;
278 }
279 ws.async_read(inBuffer, [this, self(shared_from_this())](
280 const boost::beast::error_code& ec,
281 size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700282 if (ec)
283 {
Lei YUad6dd392024-09-12 11:07:23 +0000284 if (ec != boost::beast::websocket::error::closed &&
285 ec != boost::asio::error::eof &&
286 ec != boost::asio::ssl::error::stream_truncated)
Ed Tanous002d39b2022-05-31 08:59:27 -0700287 {
Ed Tanous62598e32023-07-17 17:06:25 -0700288 BMCWEB_LOG_ERROR("doRead error {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700289 }
290 if (closeHandler)
291 {
Ed Tanous079360a2022-06-29 10:05:19 -0700292 std::string reason{ws.reason().reason.c_str()};
293 closeHandler(*this, reason);
Ed Tanous002d39b2022-05-31 08:59:27 -0700294 }
295 return;
296 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800297
298 handleMessage(bytesRead);
Ed Tanous002d39b2022-05-31 08:59:27 -0700299 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700300 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700301 void doWrite()
302 {
303 // If we're already doing a write, ignore the request, it will be picked
304 // up when the current write is complete
305 if (doingWrite)
306 {
307 return;
308 }
309
Ed Tanous863c1c22022-02-21 21:33:06 -0800310 if (outBuffer.size() == 0)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700311 {
312 // Done for now
313 return;
314 }
315 doingWrite = true;
Ed Tanous863c1c22022-02-21 21:33:06 -0800316 ws.async_write(outBuffer.data(), [this, self(shared_from_this())](
317 const boost::beast::error_code& ec,
318 size_t bytesSent) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700319 doingWrite = false;
Ed Tanous863c1c22022-02-21 21:33:06 -0800320 outBuffer.consume(bytesSent);
Ed Tanous002d39b2022-05-31 08:59:27 -0700321 if (ec == boost::beast::websocket::error::closed)
322 {
323 // Do nothing here. doRead handler will call the
324 // closeHandler.
325 close("Write error");
326 return;
327 }
328 if (ec)
329 {
Ed Tanous62598e32023-07-17 17:06:25 -0700330 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700331 return;
332 }
333 doWrite();
334 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700335 }
336
337 private:
Ed Tanous863c1c22022-02-21 21:33:06 -0800338 void handleMessage(size_t bytesRead)
339 {
340 if (messageExHandler)
341 {
342 // Note, because of the interactions with the read buffers,
343 // this message handler overrides the normal message handler
344 messageExHandler(*this, inString, MessageType::Binary,
345 [this, self(shared_from_this()), bytesRead]() {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400346 if (self == nullptr)
347 {
348 return;
349 }
Ed Tanous863c1c22022-02-21 21:33:06 -0800350
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400351 inBuffer.consume(bytesRead);
352 inString.clear();
Ed Tanous863c1c22022-02-21 21:33:06 -0800353
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400354 doRead();
355 });
Ed Tanous863c1c22022-02-21 21:33:06 -0800356 return;
357 }
358
359 if (messageHandler)
360 {
361 messageHandler(*this, inString, ws.got_text());
362 }
363 inBuffer.consume(bytesRead);
364 inString.clear();
365 doRead();
366 }
367
Ninad Palsule052bcbf2023-05-30 11:10:58 -0500368 boost::urls::url uri;
369
Ed Tanous2aee6ca2021-02-01 09:52:17 -0800370 boost::beast::websocket::stream<Adaptor, false> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700371
Ed Tanous863c1c22022-02-21 21:33:06 -0800372 bool readingDefered = false;
Ed Tanous609145a2018-09-05 16:27:36 -0700373 std::string inString;
374 boost::asio::dynamic_string_buffer<std::string::value_type,
375 std::string::traits_type,
376 std::string::allocator_type>
377 inBuffer;
Ed Tanous863c1c22022-02-21 21:33:06 -0800378
379 boost::beast::multi_buffer outBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700380 bool doingWrite = false;
381
zhanghch0577726382021-10-21 14:07:57 +0800382 std::function<void(Connection&)> openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700383 std::function<void(Connection&, const std::string&, bool)> messageHandler;
Ed Tanous863c1c22022-02-21 21:33:06 -0800384 std::function<void(crow::websocket::Connection&, std::string_view,
385 crow::websocket::MessageType type,
386 std::function<void()>&& whenComplete)>
387 messageExHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700388 std::function<void(Connection&, const std::string&)> closeHandler;
389 std::function<void(Connection&)> errorHandler;
Ed Tanous52cc1122020-07-18 13:51:21 -0700390 std::shared_ptr<persistent_data::UserSession> session;
Ed Tanous863c1c22022-02-21 21:33:06 -0800391
392 std::shared_ptr<Connection> selfOwned;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700393};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700394} // namespace websocket
395} // namespace crow