1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 #include "bmcweb_config.h" 5 6 #include "boost_formatters.hpp" 7 #include "http_body.hpp" 8 #include "http_request.hpp" 9 #include "logging.hpp" 10 #include "ossl_random.hpp" 11 #include "sessions.hpp" 12 #include "websocket.hpp" 13 14 #include <boost/asio/buffer.hpp> 15 #include <boost/asio/error.hpp> 16 #include <boost/asio/ssl/error.hpp> 17 #include <boost/beast/core/error.hpp> 18 #include <boost/beast/core/multi_buffer.hpp> 19 #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) 30 #include <boost/beast/websocket/ssl.hpp> 31 32 #include <cstddef> 33 #include <functional> 34 #include <memory> 35 #include <string> 36 #include <string_view> 37 #include <utility> 38 39 namespace crow 40 { 41 namespace websocket 42 { 43 44 template <typename Adaptor> 45 class ConnectionImpl : public Connection 46 { 47 using self_t = ConnectionImpl<Adaptor>; 48 49 public: ConnectionImpl(const boost::urls::url_view & urlViewIn,const std::shared_ptr<persistent_data::UserSession> & sessionIn,Adaptor adaptorIn,std::function<void (Connection &)> openHandlerIn,std::function<void (Connection &,const std::string &,bool)> messageHandlerIn,std::function<void (crow::websocket::Connection &,std::string_view,crow::websocket::MessageType type,std::function<void ()> && whenComplete)> messageExHandlerIn,std::function<void (Connection &,const std::string &)> closeHandlerIn,std::function<void (Connection &)> errorHandlerIn)50 ConnectionImpl( 51 const boost::urls::url_view& urlViewIn, 52 const std::shared_ptr<persistent_data::UserSession>& sessionIn, 53 Adaptor adaptorIn, std::function<void(Connection&)> openHandlerIn, 54 std::function<void(Connection&, const std::string&, bool)> 55 messageHandlerIn, 56 std::function<void(crow::websocket::Connection&, std::string_view, 57 crow::websocket::MessageType type, 58 std::function<void()>&& whenComplete)> 59 messageExHandlerIn, 60 std::function<void(Connection&, const std::string&)> closeHandlerIn, 61 std::function<void(Connection&)> errorHandlerIn) : 62 uri(urlViewIn), ws(std::move(adaptorIn)), inBuffer(inString, 131088), 63 openHandler(std::move(openHandlerIn)), 64 messageHandler(std::move(messageHandlerIn)), 65 messageExHandler(std::move(messageExHandlerIn)), 66 closeHandler(std::move(closeHandlerIn)), 67 errorHandler(std::move(errorHandlerIn)), session(sessionIn) 68 { 69 /* Turn on the timeouts on websocket stream to server role */ 70 ws.set_option(boost::beast::websocket::stream_base::timeout::suggested( 71 boost::beast::role_type::server)); 72 BMCWEB_LOG_DEBUG("Creating new connection {}", logPtr(this)); 73 } 74 start(const crow::Request & req)75 void start(const crow::Request& req) 76 { 77 BMCWEB_LOG_DEBUG("starting connection {}", logPtr(this)); 78 79 using bf = boost::beast::http::field; 80 std::string protocolHeader{ 81 req.getHeaderValue(bf::sec_websocket_protocol)}; 82 83 ws.set_option(boost::beast::websocket::stream_base::decorator( 84 [session{session}, 85 protocolHeader](boost::beast::websocket::response_type& m) { 86 if constexpr (!BMCWEB_INSECURE_DISABLE_CSRF) 87 { 88 if (session != nullptr) 89 { 90 // use protocol for csrf checking 91 if (session->cookieAuth && 92 !bmcweb::constantTimeStringCompare( 93 protocolHeader, session->csrfToken)) 94 { 95 BMCWEB_LOG_ERROR("Websocket CSRF error"); 96 m.result(boost::beast::http::status::unauthorized); 97 return; 98 } 99 } 100 } 101 if (!protocolHeader.empty()) 102 { 103 m.insert(bf::sec_websocket_protocol, protocolHeader); 104 } 105 106 m.insert(bf::strict_transport_security, 107 "max-age=31536000; " 108 "includeSubdomains; " 109 "preload"); 110 m.insert(bf::pragma, "no-cache"); 111 m.insert(bf::cache_control, "no-Store,no-Cache"); 112 m.insert("Content-Security-Policy", "default-src 'self'"); 113 m.insert("X-XSS-Protection", "1; " 114 "mode=block"); 115 m.insert("X-Content-Type-Options", "nosniff"); 116 })); 117 118 // Make a pointer to keep the req alive while we accept it. 119 using Body = boost::beast::http::request<bmcweb::HttpBody>; 120 std::unique_ptr<Body> mobile = std::make_unique<Body>(req.req); 121 Body* ptr = mobile.get(); 122 // Perform the websocket upgrade 123 ws.async_accept(*ptr, 124 std::bind_front(&self_t::acceptDone, this, 125 shared_from_this(), std::move(mobile))); 126 } 127 sendBinary(std::string_view msg)128 void sendBinary(std::string_view msg) override 129 { 130 ws.binary(true); 131 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 132 boost::asio::buffer(msg))); 133 doWrite(); 134 } 135 sendEx(MessageType type,std::string_view msg,std::function<void ()> && onDone)136 void sendEx(MessageType type, std::string_view msg, 137 std::function<void()>&& onDone) override 138 { 139 if (doingWrite) 140 { 141 BMCWEB_LOG_CRITICAL( 142 "Cannot mix sendEx usage with sendBinary or sendText"); 143 onDone(); 144 return; 145 } 146 ws.binary(type == MessageType::Binary); 147 148 ws.async_write(boost::asio::buffer(msg), 149 [weak(weak_from_this()), onDone{std::move(onDone)}]( 150 const boost::beast::error_code& ec, size_t) { 151 std::shared_ptr<Connection> self = weak.lock(); 152 if (!self) 153 { 154 BMCWEB_LOG_ERROR("Connection went away"); 155 return; 156 } 157 158 // Call the done handler regardless of whether we 159 // errored, but before we close things out 160 onDone(); 161 162 if (ec) 163 { 164 BMCWEB_LOG_ERROR("Error in ws.async_write {}", 165 ec); 166 self->close("write error"); 167 } 168 }); 169 } 170 sendText(std::string_view msg)171 void sendText(std::string_view msg) override 172 { 173 ws.text(true); 174 outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), 175 boost::asio::buffer(msg))); 176 doWrite(); 177 } 178 close(std::string_view msg)179 void close(std::string_view msg) override 180 { 181 ws.async_close( 182 {boost::beast::websocket::close_code::normal, msg}, 183 [self(shared_from_this())](const boost::system::error_code& ec) { 184 if (ec == boost::asio::error::operation_aborted) 185 { 186 return; 187 } 188 if (ec) 189 { 190 BMCWEB_LOG_ERROR("Error closing websocket {}", ec); 191 return; 192 } 193 }); 194 } 195 url()196 boost::urls::url_view url() override 197 { 198 return uri; 199 } 200 acceptDone(const std::shared_ptr<Connection> &,const std::unique_ptr<boost::beast::http::request<bmcweb::HttpBody>> &,const boost::system::error_code & ec)201 void acceptDone(const std::shared_ptr<Connection>& /*self*/, 202 const std::unique_ptr< 203 boost::beast::http::request<bmcweb::HttpBody>>& /*req*/, 204 const boost::system::error_code& ec) 205 { 206 if (ec) 207 { 208 BMCWEB_LOG_ERROR("Error in ws.async_accept {}", ec); 209 return; 210 } 211 BMCWEB_LOG_DEBUG("Websocket accepted connection"); 212 213 if (openHandler) 214 { 215 openHandler(*this); 216 } 217 doRead(); 218 } 219 deferRead()220 void deferRead() override 221 { 222 readingDefered = true; 223 224 // If we're not actively reading, we need to take ownership of 225 // ourselves for a small portion of time, do that, and clear when we 226 // resume. 227 selfOwned = shared_from_this(); 228 } 229 resumeRead()230 void resumeRead() override 231 { 232 readingDefered = false; 233 doRead(); 234 235 // No longer need to keep ourselves alive now that read is active. 236 selfOwned.reset(); 237 } 238 afterRead(const std::shared_ptr<Connection> &,const boost::beast::error_code & ec,size_t bytesRead)239 void afterRead(const std::shared_ptr<Connection>& /*self*/, 240 const boost::beast::error_code& ec, size_t bytesRead) 241 { 242 if (ec) 243 { 244 if (ec == boost::beast::error::timeout) 245 { 246 BMCWEB_LOG_WARNING("doRead timeout: {}", ec); 247 } 248 else if (ec == boost::asio::error::operation_aborted) 249 { 250 BMCWEB_LOG_WARNING("doRead operation is aborted: {}", ec); 251 } 252 else if (ec != boost::beast::websocket::error::closed && 253 ec != boost::asio::error::eof && 254 ec != boost::asio::ssl::error::stream_truncated) 255 { 256 BMCWEB_LOG_ERROR("doRead error {}", ec); 257 } 258 259 if (closeHandler) 260 { 261 std::string reason{ws.reason().reason.c_str()}; 262 closeHandler(*this, reason); 263 } 264 return; 265 } 266 267 handleMessage(bytesRead); 268 } 269 doRead()270 void doRead() 271 { 272 if (readingDefered) 273 { 274 return; 275 } 276 ws.async_read(inBuffer, std::bind_front(&self_t::afterRead, this, 277 shared_from_this())); 278 } 279 afterWrite(const std::shared_ptr<Connection> &,const boost::beast::error_code & ec,size_t bytesSent)280 void afterWrite(const std::shared_ptr<Connection>& /*self*/, 281 const boost::beast::error_code& ec, size_t bytesSent) 282 { 283 doingWrite = false; 284 outBuffer.consume(bytesSent); 285 if (ec) 286 { 287 if (ec == boost::beast::websocket::error::closed) 288 { 289 // Do nothing here. doRead handler will call the 290 // closeHandler. 291 close("Write error"); 292 } 293 else if (ec == boost::asio::error::operation_aborted) 294 { 295 BMCWEB_LOG_WARNING("doWrite operation is aborted: {}", ec); 296 } 297 else 298 { 299 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec); 300 } 301 return; 302 } 303 doWrite(); 304 } 305 doWrite()306 void doWrite() 307 { 308 // If we're already doing a write, ignore the request, it will be picked 309 // up when the current write is complete 310 if (doingWrite) 311 { 312 return; 313 } 314 315 if (outBuffer.size() == 0) 316 { 317 // Done for now 318 return; 319 } 320 doingWrite = true; 321 ws.async_write( 322 outBuffer.data(), 323 std::bind_front(&self_t::afterWrite, this, shared_from_this())); 324 } 325 326 private: handleMessage(size_t bytesRead)327 void handleMessage(size_t bytesRead) 328 { 329 if (messageExHandler) 330 { 331 // Note, because of the interactions with the read buffers, 332 // this message handler overrides the normal message handler 333 messageExHandler(*this, inString, MessageType::Binary, 334 [this, self(shared_from_this()), bytesRead]() { 335 if (self == nullptr) 336 { 337 return; 338 } 339 340 inBuffer.consume(bytesRead); 341 inString.clear(); 342 343 doRead(); 344 }); 345 return; 346 } 347 348 if (messageHandler) 349 { 350 messageHandler(*this, inString, ws.got_text()); 351 } 352 inBuffer.consume(bytesRead); 353 inString.clear(); 354 doRead(); 355 } 356 357 boost::urls::url uri; 358 359 boost::beast::websocket::stream<Adaptor, false> ws; 360 361 bool readingDefered = false; 362 std::string inString; 363 boost::asio::dynamic_string_buffer<std::string::value_type, 364 std::string::traits_type, 365 std::string::allocator_type> 366 inBuffer; 367 368 boost::beast::multi_buffer outBuffer; 369 bool doingWrite = false; 370 371 std::function<void(Connection&)> openHandler; 372 std::function<void(Connection&, const std::string&, bool)> messageHandler; 373 std::function<void(crow::websocket::Connection&, std::string_view, 374 crow::websocket::MessageType type, 375 std::function<void()>&& whenComplete)> 376 messageExHandler; 377 std::function<void(Connection&, const std::string&)> closeHandler; 378 std::function<void(Connection&)> errorHandler; 379 std::shared_ptr<persistent_data::UserSession> session; 380 381 std::shared_ptr<Connection> selfOwned; 382 }; 383 } // namespace websocket 384 } // namespace crow 385