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 doRead()239 void doRead() 240 { 241 if (readingDefered) 242 { 243 return; 244 } 245 ws.async_read(inBuffer, [this, self(shared_from_this())]( 246 const boost::beast::error_code& ec, 247 size_t bytesRead) { 248 if (ec) 249 { 250 if (ec != boost::beast::websocket::error::closed && 251 ec != boost::asio::error::eof && 252 ec != boost::asio::ssl::error::stream_truncated) 253 { 254 BMCWEB_LOG_ERROR("doRead error {}", ec); 255 } 256 if (closeHandler) 257 { 258 std::string reason{ws.reason().reason.c_str()}; 259 closeHandler(*this, reason); 260 } 261 return; 262 } 263 264 handleMessage(bytesRead); 265 }); 266 } doWrite()267 void doWrite() 268 { 269 // If we're already doing a write, ignore the request, it will be picked 270 // up when the current write is complete 271 if (doingWrite) 272 { 273 return; 274 } 275 276 if (outBuffer.size() == 0) 277 { 278 // Done for now 279 return; 280 } 281 doingWrite = true; 282 ws.async_write(outBuffer.data(), [this, self(shared_from_this())]( 283 const boost::beast::error_code& ec, 284 size_t bytesSent) { 285 doingWrite = false; 286 outBuffer.consume(bytesSent); 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 return; 293 } 294 if (ec) 295 { 296 BMCWEB_LOG_ERROR("Error in ws.async_write {}", ec); 297 return; 298 } 299 doWrite(); 300 }); 301 } 302 303 private: handleMessage(size_t bytesRead)304 void handleMessage(size_t bytesRead) 305 { 306 if (messageExHandler) 307 { 308 // Note, because of the interactions with the read buffers, 309 // this message handler overrides the normal message handler 310 messageExHandler(*this, inString, MessageType::Binary, 311 [this, self(shared_from_this()), bytesRead]() { 312 if (self == nullptr) 313 { 314 return; 315 } 316 317 inBuffer.consume(bytesRead); 318 inString.clear(); 319 320 doRead(); 321 }); 322 return; 323 } 324 325 if (messageHandler) 326 { 327 messageHandler(*this, inString, ws.got_text()); 328 } 329 inBuffer.consume(bytesRead); 330 inString.clear(); 331 doRead(); 332 } 333 334 boost::urls::url uri; 335 336 boost::beast::websocket::stream<Adaptor, false> ws; 337 338 bool readingDefered = false; 339 std::string inString; 340 boost::asio::dynamic_string_buffer<std::string::value_type, 341 std::string::traits_type, 342 std::string::allocator_type> 343 inBuffer; 344 345 boost::beast::multi_buffer outBuffer; 346 bool doingWrite = false; 347 348 std::function<void(Connection&)> openHandler; 349 std::function<void(Connection&, const std::string&, bool)> messageHandler; 350 std::function<void(crow::websocket::Connection&, std::string_view, 351 crow::websocket::MessageType type, 352 std::function<void()>&& whenComplete)> 353 messageExHandler; 354 std::function<void(Connection&, const std::string&)> closeHandler; 355 std::function<void(Connection&)> errorHandler; 356 std::shared_ptr<persistent_data::UserSession> session; 357 358 std::shared_ptr<Connection> selfOwned; 359 }; 360 } // namespace websocket 361 } // namespace crow 362