blob: 12fbb8ed10fec0ba7a88a390d813a9ba0edbe9e0 [file] [log] [blame]
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +02001/*
2// Copyright (c) 2019 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#pragma once
17#include <app.h>
18#include <websocket.h>
19
20#include <boost/asio.hpp>
21#include <boost/beast/core/buffers_to_string.hpp>
22#include <boost/beast/core/multi_buffer.hpp>
23#include <boost/container/flat_map.hpp>
24#include <dbus_utility.hpp>
25#include <experimental/filesystem>
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010026#include <privileges.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020027#include <variant>
28#include <webserver_common.hpp>
29
30namespace crow
31{
32
33namespace nbd_proxy
34{
35
36using boost::asio::local::stream_protocol;
37
38static constexpr auto nbdBufferSize = 131088;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010039static const char* requiredPrivilegeString = "ConfigureManager";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020040
41struct NbdProxyServer : std::enable_shared_from_this<NbdProxyServer>
42{
43 NbdProxyServer(crow::websocket::Connection& connIn,
44 const std::string& socketIdIn,
45 const std::string& endpointIdIn, const std::string& pathIn) :
46 socketId(socketIdIn),
47 endpointId(endpointIdIn), path(pathIn),
48 acceptor(connIn.get_io_context(), stream_protocol::endpoint(socketId)),
49 connection(connIn)
50 {
51 }
52
53 ~NbdProxyServer()
54 {
55 BMCWEB_LOG_DEBUG << "NbdProxyServer destructor";
56 close();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020057 }
58
59 std::string getEndpointId() const
60 {
61 return endpointId;
62 }
63
64 void run()
65 {
66 acceptor.async_accept(
67 [this, self(shared_from_this())](boost::system::error_code ec,
68 stream_protocol::socket socket) {
69 if (ec)
70 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010071 BMCWEB_LOG_ERROR << "UNIX socket: async_accept error = "
72 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020073 return;
74 }
75 if (peerSocket)
76 {
77 // Something is wrong - socket shouldn't be acquired at this
78 // point
79 BMCWEB_LOG_ERROR
80 << "Failed to open connection - socket already used";
81 return;
82 }
83
84 BMCWEB_LOG_DEBUG << "Connection opened";
85 peerSocket = std::move(socket);
86 doRead();
87
88 // Trigger Write if any data was sent from server
89 // Initially this is negotiation chunk
90 doWrite();
91 });
92
93 auto mountHandler = [](const boost::system::error_code ec,
94 const bool status) {
95 if (ec)
96 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010097 BMCWEB_LOG_ERROR << "DBus error: cannot call mount method = "
98 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020099 return;
100 }
101 };
102
103 crow::connections::systemBus->async_method_call(
104 std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path,
105 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount");
106 }
107
108 void send(const std::string_view data)
109 {
110 boost::asio::buffer_copy(ws2uxBuf.prepare(data.size()),
111 boost::asio::buffer(data));
112 ws2uxBuf.commit(data.size());
113 doWrite();
114 }
115
116 void close()
117 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100118 acceptor.close();
119 if (peerSocket)
120 {
121 BMCWEB_LOG_DEBUG << "peerSocket->close()";
122 peerSocket->close();
123 peerSocket.reset();
124 BMCWEB_LOG_DEBUG << "std::remove(" << socketId << ")";
125 std::remove(socketId.c_str());
126 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200127 // The reference to session should exists until unmount is
128 // called
129 auto unmountHandler = [](const boost::system::error_code ec) {
130 if (ec)
131 {
132 BMCWEB_LOG_ERROR << "DBus error: " << ec
133 << ", cannot call unmount method";
134 return;
135 }
136 };
137
138 crow::connections::systemBus->async_method_call(
139 std::move(unmountHandler), "xyz.openbmc_project.VirtualMedia", path,
140 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
141 }
142
143 private:
144 void doRead()
145 {
146 if (!peerSocket)
147 {
148 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
149 // Skip if UNIX socket is not created yet.
150 return;
151 }
152
153 // Trigger async read
154 peerSocket->async_read_some(
155 ux2wsBuf.prepare(nbdBufferSize),
156 [this, self(shared_from_this())](boost::system::error_code ec,
157 std::size_t bytesRead) {
158 if (ec)
159 {
160 BMCWEB_LOG_ERROR << "UNIX socket: async_read_some error = "
Iwona Winiarska123e8232019-11-29 12:34:33 +0100161 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200162 // UNIX socket has been closed by peer, best we can do is to
163 // break all connections
164 close();
165 return;
166 }
167
168 // Fetch data from UNIX socket
169
170 ux2wsBuf.commit(bytesRead);
171
172 // Paste it to WebSocket as binary
173 connection.sendBinary(
174 boost::beast::buffers_to_string(ux2wsBuf.data()));
175 ux2wsBuf.consume(bytesRead);
176
177 // Allow further reads
178 doRead();
179 });
180 }
181
182 void doWrite()
183 {
184 if (!peerSocket)
185 {
186 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
187 // Skip if UNIX socket is not created yet. Collect data, and wait
188 // for nbd-client connection
189 return;
190 }
191
192 if (uxWriteInProgress)
193 {
194 BMCWEB_LOG_ERROR << "Write in progress";
195 return;
196 }
197
198 if (ws2uxBuf.size() == 0)
199 {
200 BMCWEB_LOG_ERROR << "No data to write to UNIX socket";
201 return;
202 }
203
204 uxWriteInProgress = true;
205 boost::asio::async_write(
206 *peerSocket, ws2uxBuf.data(),
207 [this, self(shared_from_this())](boost::system::error_code ec,
208 std::size_t bytesWritten) {
209 ws2uxBuf.consume(bytesWritten);
210 uxWriteInProgress = false;
211 if (ec)
212 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100213 BMCWEB_LOG_ERROR << "UNIX: async_write error = "
214 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200215 return;
216 }
217 // Retrigger doWrite if there is something in buffer
Iwona Winiarska123e8232019-11-29 12:34:33 +0100218 if (ws2uxBuf.size() > 0)
219 {
220 doWrite();
221 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200222 });
223 }
224
225 // Keeps UNIX socket endpoint file path
226 const std::string socketId;
227 const std::string endpointId;
228 const std::string path;
229
230 bool uxWriteInProgress = false;
231
232 // UNIX => WebSocket buffer
233 boost::beast::multi_buffer ux2wsBuf;
234
235 // WebSocket <= UNIX buffer
236 boost::beast::multi_buffer ws2uxBuf;
237
238 // Default acceptor for UNIX socket
239 stream_protocol::acceptor acceptor;
240
241 // The socket used to communicate with the client.
242 std::optional<stream_protocol::socket> peerSocket;
243
244 crow::websocket::Connection& connection;
245};
246
247static boost::container::flat_map<crow::websocket::Connection*,
248 std::shared_ptr<NbdProxyServer>>
249 sessions;
250
251void requestRoutes(CrowApp& app)
252{
253 BMCWEB_ROUTE(app, "/nbd/<str>")
254 .websocket()
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100255 .onopen([](crow::websocket::Connection& conn,
256 std::shared_ptr<bmcweb::AsyncResp> asyncResp) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200257 BMCWEB_LOG_DEBUG << "nbd-proxy.onopen(" << &conn << ")";
258
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100259 auto getUserInfoHandler = [&conn, asyncResp{std::move(asyncResp)}](
260 const boost::system::error_code ec,
261 boost::container::flat_map<
262 std::string,
263 std::variant<
264 bool, std::string,
265 std::vector<std::string>>>
266 userInfo) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200267 if (ec)
268 {
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100269 BMCWEB_LOG_ERROR << "GetUserInfo failed...";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200270 asyncResp->res.result(
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100271 boost::beast::http::status::internal_server_error);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200272 return;
273 }
274
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100275 const std::string* userRolePtr = nullptr;
276 auto userInfoIter = userInfo.find("UserPrivilege");
277 if (userInfoIter != userInfo.end())
278 {
279 userRolePtr =
280 std::get_if<std::string>(&userInfoIter->second);
281 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200282
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100283 std::string userRole{};
284 if (userRolePtr != nullptr)
285 {
286 userRole = *userRolePtr;
287 BMCWEB_LOG_DEBUG << "userName = " << conn.getUserName()
288 << " userRole = " << *userRolePtr;
289 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200290
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100291 // Get the user privileges from the role
292 ::redfish::Privileges userPrivileges =
293 ::redfish::getUserPrivileges(userRole);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200294
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100295 const ::redfish::Privileges requiredPrivileges{
296 requiredPrivilegeString};
297
298 if (!userPrivileges.isSupersetOf(requiredPrivileges))
299 {
300 BMCWEB_LOG_DEBUG << "User " << conn.getUserName()
301 << " not authorized for nbd connection";
302 asyncResp->res.result(
303 boost::beast::http::status::unauthorized);
304 return;
305 }
306
307 for (const auto session : sessions)
308 {
309 if (session.second->getEndpointId() == conn.req.target())
310 {
311 BMCWEB_LOG_ERROR
312 << "Cannot open new connection - socket is in use";
313 asyncResp->res.result(
314 boost::beast::http::status::bad_request);
315 return;
316 }
317 }
318
319 auto openHandler = [asyncResp,
320 &conn](const boost::system::error_code ec,
321 dbus::utility::ManagedObjectType&
322 objects) {
323 const std::string* socketValue = nullptr;
324 const std::string* endpointValue = nullptr;
325 const std::string* endpointObjectPath = nullptr;
326
327 if (ec)
328 {
329 BMCWEB_LOG_ERROR << "DBus error: " << ec.message();
330 asyncResp->res.result(
331 boost::beast::http::status::internal_server_error);
332 return;
333 }
334
335 for (const auto& objectPath : objects)
336 {
337 const auto interfaceMap = objectPath.second.find(
338 "xyz.openbmc_project.VirtualMedia.MountPoint");
339
340 if (interfaceMap == objectPath.second.end())
341 {
342 BMCWEB_LOG_DEBUG << "Cannot find MountPoint object";
343 continue;
344 }
345
346 const auto endpoint =
347 interfaceMap->second.find("EndpointId");
348 if (endpoint == interfaceMap->second.end())
349 {
350 BMCWEB_LOG_DEBUG
351 << "Cannot find EndpointId property";
352 continue;
353 }
354
355 endpointValue =
356 std::get_if<std::string>(&endpoint->second);
357
358 if (endpointValue == nullptr)
359 {
360 BMCWEB_LOG_ERROR
361 << "EndpointId property value is null";
362 continue;
363 }
364
365 if (*endpointValue == conn.req.target())
366 {
367 const auto socket =
368 interfaceMap->second.find("Socket");
369 if (socket == interfaceMap->second.end())
370 {
371 BMCWEB_LOG_DEBUG
372 << "Cannot find Socket property";
373 continue;
374 }
375
376 socketValue =
377 std::get_if<std::string>(&socket->second);
378 if (socketValue == nullptr)
379 {
380 BMCWEB_LOG_ERROR
381 << "Socket property value is null";
382 continue;
383 }
384
385 endpointObjectPath = &objectPath.first.str;
386 break;
387 }
388 }
389
390 if (endpointObjectPath == nullptr)
391 {
392 BMCWEB_LOG_ERROR << "Cannot find requested EndpointId";
393 asyncResp->res.result(
394 boost::beast::http::status::not_found);
395 return;
396 }
397
398 // If the socket file exists (i.e. after bmcweb crash),
399 // we cannot reuse it.
400 std::remove((*socketValue).c_str());
401
402 sessions[&conn] = std::make_shared<NbdProxyServer>(
403 conn, std::move(*socketValue),
404 std::move(*endpointValue),
405 std::move(*endpointObjectPath));
406
407 sessions[&conn]->run();
408
409 asyncResp->res.result(boost::beast::http::status::ok);
410 };
411 crow::connections::systemBus->async_method_call(
412 std::move(openHandler), "xyz.openbmc_project.VirtualMedia",
413 "/xyz/openbmc_project/VirtualMedia",
414 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200415 };
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100416
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200417 crow::connections::systemBus->async_method_call(
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100418 std::move(getUserInfoHandler),
419 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
420 "xyz.openbmc_project.User.Manager", "GetUserInfo",
421 conn.getUserName());
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200422 })
423 .onclose(
424 [](crow::websocket::Connection& conn, const std::string& reason) {
425 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason
426 << "')";
427 auto session = sessions.find(&conn);
428 if (session == sessions.end())
429 {
430 BMCWEB_LOG_DEBUG << "No session to close";
431 return;
432 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200433 session->second->close();
Iwona Winiarska123e8232019-11-29 12:34:33 +0100434 // Remove reference to session in global map
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200435 sessions.erase(session);
436 })
437 .onmessage([](crow::websocket::Connection& conn,
438 const std::string& data, bool isBinary) {
439 BMCWEB_LOG_DEBUG << "nbd-proxy.onmessage(len = " << data.length()
440 << ")";
441 // Acquire proxy from sessions
442 auto session = sessions.find(&conn);
443 if (session != sessions.end())
444 {
445 if (session->second)
446 {
447 session->second->send(data);
448 return;
449 }
450 }
451 conn.close();
452 });
453}
454} // namespace nbd_proxy
455} // namespace crow