blob: fbbf3cce13ffe82ea13db78653826053c874bbf8 [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
Ed Tanous3ccb3ad2023-01-13 17:40:03 -080017#include "app.hpp"
18#include "dbus_utility.hpp"
19#include "privileges.hpp"
20
Ed Tanousd43cd0c2020-09-30 20:46:53 -070021#include <boost/asio/buffer.hpp>
22#include <boost/asio/local/stream_protocol.hpp>
23#include <boost/asio/write.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020024#include <boost/beast/core/buffers_to_string.hpp>
25#include <boost/beast/core/multi_buffer.hpp>
26#include <boost/container/flat_map.hpp>
Ed Tanous04e438c2020-10-03 08:06:26 -070027#include <websocket.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020028
29namespace crow
30{
31
32namespace nbd_proxy
33{
34
35using boost::asio::local::stream_protocol;
36
37static constexpr auto nbdBufferSize = 131088;
Ed Tanouscf9e4172022-12-21 09:30:16 -080038constexpr const char* requiredPrivilegeString = "ConfigureManager";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020039
40struct NbdProxyServer : std::enable_shared_from_this<NbdProxyServer>
41{
42 NbdProxyServer(crow::websocket::Connection& connIn,
43 const std::string& socketIdIn,
44 const std::string& endpointIdIn, const std::string& pathIn) :
45 socketId(socketIdIn),
46 endpointId(endpointIdIn), path(pathIn),
Ed Tanous2c70f802020-09-28 14:29:23 -070047 acceptor(connIn.getIoContext(), stream_protocol::endpoint(socketId)),
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020048 connection(connIn)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050049 {}
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020050
Ed Tanous08e9fa92022-02-16 16:18:08 -080051 NbdProxyServer(const NbdProxyServer&) = delete;
52 NbdProxyServer(NbdProxyServer&&) = delete;
53 NbdProxyServer& operator=(const NbdProxyServer&) = delete;
54 NbdProxyServer& operator=(NbdProxyServer&&) = delete;
55
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020056 ~NbdProxyServer()
57 {
58 BMCWEB_LOG_DEBUG << "NbdProxyServer destructor";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020059 }
60
61 std::string getEndpointId() const
62 {
63 return endpointId;
64 }
65
66 void run()
67 {
Ed Tanous5e7e2dc2023-02-16 10:37:01 -080068 acceptor.async_accept([this, self(shared_from_this())](
69 const boost::system::error_code& ec,
70 stream_protocol::socket socket) {
Ed Tanous002d39b2022-05-31 08:59:27 -070071 if (ec)
72 {
73 BMCWEB_LOG_ERROR << "UNIX socket: async_accept error = "
74 << ec.message();
75 return;
76 }
77 if (peerSocket)
78 {
79 // Something is wrong - socket shouldn't be acquired at this
80 // point
81 BMCWEB_LOG_ERROR
82 << "Failed to open connection - socket already used";
83 return;
84 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020085
Ed Tanous002d39b2022-05-31 08:59:27 -070086 BMCWEB_LOG_DEBUG << "Connection opened";
87 peerSocket = std::move(socket);
88 doRead();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020089
Ed Tanous002d39b2022-05-31 08:59:27 -070090 // Trigger Write if any data was sent from server
91 // Initially this is negotiation chunk
92 doWrite();
93 });
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020094
Ed Tanous002d39b2022-05-31 08:59:27 -070095 auto mountHandler =
Ed Tanous5e7e2dc2023-02-16 10:37:01 -080096 [this, self(shared_from_this())](
97 const boost::system::error_code& ec, const bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020098 if (ec)
99 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100100 BMCWEB_LOG_ERROR << "DBus error: cannot call mount method = "
101 << ec.message();
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200102 connection.close("Failed to mount media");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200103 return;
104 }
105 };
106
107 crow::connections::systemBus->async_method_call(
108 std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path,
109 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount");
110 }
111
Ed Tanous26ccae32023-02-16 10:28:44 -0800112 void send(std::string_view data)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200113 {
114 boost::asio::buffer_copy(ws2uxBuf.prepare(data.size()),
115 boost::asio::buffer(data));
116 ws2uxBuf.commit(data.size());
117 doWrite();
118 }
119
120 void close()
121 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100122 acceptor.close();
123 if (peerSocket)
124 {
125 BMCWEB_LOG_DEBUG << "peerSocket->close()";
126 peerSocket->close();
127 peerSocket.reset();
128 BMCWEB_LOG_DEBUG << "std::remove(" << socketId << ")";
129 std::remove(socketId.c_str());
130 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200131 // The reference to session should exists until unmount is
132 // called
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800133 auto unmountHandler = [](const boost::system::error_code& ec) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200134 if (ec)
135 {
136 BMCWEB_LOG_ERROR << "DBus error: " << ec
137 << ", cannot call unmount method";
138 return;
139 }
140 };
141
142 crow::connections::systemBus->async_method_call(
143 std::move(unmountHandler), "xyz.openbmc_project.VirtualMedia", path,
144 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
145 }
146
147 private:
148 void doRead()
149 {
150 if (!peerSocket)
151 {
152 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
153 // Skip if UNIX socket is not created yet.
154 return;
155 }
156
157 // Trigger async read
158 peerSocket->async_read_some(
159 ux2wsBuf.prepare(nbdBufferSize),
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800160 [this, self(shared_from_this())](
161 const boost::system::error_code& ec, std::size_t bytesRead) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700162 if (ec)
163 {
164 BMCWEB_LOG_ERROR << "UNIX socket: async_read_some error = "
165 << ec.message();
166 // UNIX socket has been closed by peer, best we can do is to
167 // break all connections
168 close();
169 return;
170 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200171
Ed Tanous002d39b2022-05-31 08:59:27 -0700172 // Fetch data from UNIX socket
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200173
Ed Tanous002d39b2022-05-31 08:59:27 -0700174 ux2wsBuf.commit(bytesRead);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200175
Ed Tanous002d39b2022-05-31 08:59:27 -0700176 // Paste it to WebSocket as binary
177 connection.sendBinary(
178 boost::beast::buffers_to_string(ux2wsBuf.data()));
179 ux2wsBuf.consume(bytesRead);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200180
Ed Tanous002d39b2022-05-31 08:59:27 -0700181 // Allow further reads
182 doRead();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200183 });
184 }
185
186 void doWrite()
187 {
188 if (!peerSocket)
189 {
190 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
191 // Skip if UNIX socket is not created yet. Collect data, and wait
192 // for nbd-client connection
193 return;
194 }
195
196 if (uxWriteInProgress)
197 {
198 BMCWEB_LOG_ERROR << "Write in progress";
199 return;
200 }
201
Jason M. Bills9c0b4e52022-02-14 07:23:30 -0800202 if (ws2uxBuf.size() == 0)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200203 {
204 BMCWEB_LOG_ERROR << "No data to write to UNIX socket";
205 return;
206 }
207
208 uxWriteInProgress = true;
209 boost::asio::async_write(
210 *peerSocket, ws2uxBuf.data(),
Ed Tanous5e7e2dc2023-02-16 10:37:01 -0800211 [this, self(shared_from_this())](
212 const boost::system::error_code& ec, std::size_t bytesWritten) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700213 ws2uxBuf.consume(bytesWritten);
214 uxWriteInProgress = false;
215 if (ec)
216 {
217 BMCWEB_LOG_ERROR << "UNIX: async_write error = "
218 << ec.message();
219 return;
220 }
221 // Retrigger doWrite if there is something in buffer
222 if (ws2uxBuf.size() > 0)
223 {
224 doWrite();
225 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200226 });
227 }
228
229 // Keeps UNIX socket endpoint file path
230 const std::string socketId;
231 const std::string endpointId;
232 const std::string path;
233
234 bool uxWriteInProgress = false;
235
236 // UNIX => WebSocket buffer
237 boost::beast::multi_buffer ux2wsBuf;
238
239 // WebSocket <= UNIX buffer
240 boost::beast::multi_buffer ws2uxBuf;
241
242 // Default acceptor for UNIX socket
243 stream_protocol::acceptor acceptor;
244
245 // The socket used to communicate with the client.
246 std::optional<stream_protocol::socket> peerSocket;
247
248 crow::websocket::Connection& connection;
249};
250
Ed Tanouscf9e4172022-12-21 09:30:16 -0800251using SessionMap = boost::container::flat_map<crow::websocket::Connection*,
252 std::shared_ptr<NbdProxyServer>>;
253// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
254static SessionMap sessions;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200255
Ed Tanous8108fd32023-02-27 13:58:03 -0800256inline void onOpen(crow::websocket::Connection& conn)
257{
258 BMCWEB_LOG_DEBUG << "nbd-proxy.onopen(" << &conn << ")";
259
260 auto getUserInfoHandler =
261 [&conn](const boost::system::error_code& ec,
262 const dbus::utility::DBusPropertiesMap& userInfo) {
263 if (ec)
264 {
265 BMCWEB_LOG_ERROR << "GetUserInfo failed...";
266 conn.close("Failed to get user information");
267 return;
268 }
269
270 const std::string* userRolePtr = nullptr;
271 auto userInfoIter =
272 std::find_if(userInfo.begin(), userInfo.end(), [](const auto& p) {
273 return p.first == "UserPrivilege";
274 });
275 if (userInfoIter != userInfo.end())
276 {
277 userRolePtr = std::get_if<std::string>(&userInfoIter->second);
278 }
279
280 std::string userRole{};
281 if (userRolePtr != nullptr)
282 {
283 userRole = *userRolePtr;
284 BMCWEB_LOG_DEBUG << "userName = " << conn.getUserName()
285 << " userRole = " << *userRolePtr;
286 }
287
288 // Get the user privileges from the role
289 ::redfish::Privileges userPrivileges =
290 ::redfish::getUserPrivileges(userRole);
291
292 const ::redfish::Privileges requiredPrivileges{requiredPrivilegeString};
293
294 if (!userPrivileges.isSupersetOf(requiredPrivileges))
295 {
296 BMCWEB_LOG_DEBUG << "User " << conn.getUserName()
297 << " not authorized for nbd connection";
298 conn.close("Unathourized access");
299 return;
300 }
301
302 auto openHandler =
303 [&conn](const boost::system::error_code& ec2,
304 const dbus::utility::ManagedObjectType& objects) {
305 const std::string* socketValue = nullptr;
306 const std::string* endpointValue = nullptr;
307 const std::string* endpointObjectPath = nullptr;
308
309 if (ec2)
310 {
311 BMCWEB_LOG_ERROR << "DBus error: " << ec2.message();
312 conn.close("Failed to create mount point");
313 return;
314 }
315
316 for (const auto& [objectPath, interfaces] : objects)
317 {
318 for (const auto& [interface, properties] : interfaces)
319 {
320 if (interface !=
321 "xyz.openbmc_project.VirtualMedia.MountPoint")
322 {
323 continue;
324 }
325
326 for (const auto& [name, value] : properties)
327 {
328 if (name == "EndpointId")
329 {
330 endpointValue = std::get_if<std::string>(&value);
331
332 if (endpointValue == nullptr)
333 {
334 BMCWEB_LOG_ERROR
335 << "EndpointId property value is null";
336 }
337 }
338 if (name == "Socket")
339 {
340 socketValue = std::get_if<std::string>(&value);
341 if (socketValue == nullptr)
342 {
343 BMCWEB_LOG_ERROR
344 << "Socket property value is null";
345 }
346 }
347 }
348 }
349
350 if ((endpointValue != nullptr) && (socketValue != nullptr) &&
351 *endpointValue == conn.req.target())
352 {
353 endpointObjectPath = &objectPath.str;
354 break;
355 }
356 }
357
358 if (objects.empty() || endpointObjectPath == nullptr)
359 {
360 BMCWEB_LOG_ERROR << "Cannot find requested EndpointId";
361 conn.close("Failed to match EndpointId");
362 return;
363 }
364
365 for (const auto& session : sessions)
366 {
367 if (session.second->getEndpointId() == conn.req.target())
368 {
369 BMCWEB_LOG_ERROR
370 << "Cannot open new connection - socket is in use";
371 conn.close("Slot is in use");
372 return;
373 }
374 }
375
376 // If the socket file exists (i.e. after bmcweb crash),
377 // we cannot reuse it.
378 std::remove((*socketValue).c_str());
379
380 sessions[&conn] = std::make_shared<NbdProxyServer>(
381 conn, *socketValue, *endpointValue, *endpointObjectPath);
382
383 sessions[&conn]->run();
384 };
385 crow::connections::systemBus->async_method_call(
386 std::move(openHandler), "xyz.openbmc_project.VirtualMedia",
387 "/xyz/openbmc_project/VirtualMedia",
388 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
389 };
390
391 crow::connections::systemBus->async_method_call(
392 std::move(getUserInfoHandler), "xyz.openbmc_project.User.Manager",
393 "/xyz/openbmc_project/user", "xyz.openbmc_project.User.Manager",
394 "GetUserInfo", conn.getUserName());
395}
396
397inline void onClose(crow::websocket::Connection& conn,
398 const std::string& reason)
399{
400 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason << "')";
401 auto session = sessions.find(&conn);
402 if (session == sessions.end())
403 {
404 BMCWEB_LOG_DEBUG << "No session to close";
405 return;
406 }
407 session->second->close();
408 // Remove reference to session in global map
409 sessions.erase(session);
410}
411
412inline void onMessage(crow::websocket::Connection& conn,
413 const std::string& data, bool /*isBinary*/)
414{
415 BMCWEB_LOG_DEBUG << "nbd-proxy.onmessage(len = " << data.length() << ")";
416 // Acquire proxy from sessions
417 auto session = sessions.find(&conn);
418 if (session != sessions.end())
419 {
420 if (session->second)
421 {
422 session->second->send(data);
423 return;
424 }
425 }
426}
427
Ed Tanous81ce6092020-12-17 16:54:55 +0000428inline void requestRoutes(App& app)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200429{
430 BMCWEB_ROUTE(app, "/nbd/<str>")
431 .websocket()
Ed Tanous8108fd32023-02-27 13:58:03 -0800432 .onopen(onOpen)
433 .onclose(onClose)
434 .onmessage(onMessage);
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200435}
436} // namespace nbd_proxy
437} // namespace crow