blob: 3472c3a5146d11c99b10191b7a78ed97f8e49e8e [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 {
68 acceptor.async_accept(
69 [this, self(shared_from_this())](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 =
96 [this, self(shared_from_this())](const boost::system::error_code ec,
97 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
112 void send(const std::string_view data)
113 {
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
133 auto unmountHandler = [](const boost::system::error_code ec) {
134 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),
160 [this, self(shared_from_this())](boost::system::error_code ec,
161 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(),
211 [this, self(shared_from_this())](boost::system::error_code ec,
212 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 Tanous81ce6092020-12-17 16:54:55 +0000256inline void requestRoutes(App& app)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200257{
258 BMCWEB_ROUTE(app, "/nbd/<str>")
259 .websocket()
zhanghch0577726382021-10-21 14:07:57 +0800260 .onopen([](crow::websocket::Connection& conn) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200261 BMCWEB_LOG_DEBUG << "nbd-proxy.onopen(" << &conn << ")";
262
Ed Tanous002d39b2022-05-31 08:59:27 -0700263 auto getUserInfoHandler =
264 [&conn](const boost::system::error_code ec,
265 const dbus::utility::DBusPropertiesMap& userInfo) {
266 if (ec)
267 {
268 BMCWEB_LOG_ERROR << "GetUserInfo failed...";
269 conn.close("Failed to get user information");
270 return;
271 }
272
273 const std::string* userRolePtr = nullptr;
274 auto userInfoIter = std::find_if(userInfo.begin(), userInfo.end(),
275 [](const auto& p) {
276 return p.first == "UserPrivilege";
277 });
278 if (userInfoIter != userInfo.end())
279 {
280 userRolePtr = std::get_if<std::string>(&userInfoIter->second);
281 }
282
283 std::string userRole{};
284 if (userRolePtr != nullptr)
285 {
286 userRole = *userRolePtr;
287 BMCWEB_LOG_DEBUG << "userName = " << conn.getUserName()
288 << " userRole = " << *userRolePtr;
289 }
290
291 // Get the user privileges from the role
292 ::redfish::Privileges userPrivileges =
293 ::redfish::getUserPrivileges(userRole);
294
295 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 conn.close("Unathourized access");
303 return;
304 }
305
306 auto openHandler =
Ed Tanouse3009e42022-02-16 15:42:37 -0800307 [&conn](const boost::system::error_code ec2,
Ed Tanous002d39b2022-05-31 08:59:27 -0700308 const dbus::utility::ManagedObjectType& objects) {
309 const std::string* socketValue = nullptr;
310 const std::string* endpointValue = nullptr;
311 const std::string* endpointObjectPath = nullptr;
312
Ed Tanouse3009e42022-02-16 15:42:37 -0800313 if (ec2)
Ed Tanous168e20c2021-12-13 14:39:53 -0800314 {
Ed Tanouse3009e42022-02-16 15:42:37 -0800315 BMCWEB_LOG_ERROR << "DBus error: " << ec2.message();
Ed Tanous002d39b2022-05-31 08:59:27 -0700316 conn.close("Failed to create mount point");
Ed Tanous168e20c2021-12-13 14:39:53 -0800317 return;
318 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100319
Ed Tanous002d39b2022-05-31 08:59:27 -0700320 for (const auto& [objectPath, interfaces] : objects)
Ed Tanous168e20c2021-12-13 14:39:53 -0800321 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700322 for (const auto& [interface, properties] : interfaces)
323 {
324 if (interface !=
325 "xyz.openbmc_project.VirtualMedia.MountPoint")
326 {
327 continue;
328 }
329
330 for (const auto& [name, value] : properties)
331 {
332 if (name == "EndpointId")
333 {
334 endpointValue =
335 std::get_if<std::string>(&value);
336
337 if (endpointValue == nullptr)
338 {
339 BMCWEB_LOG_ERROR
340 << "EndpointId property value is null";
341 }
342 }
343 if (name == "Socket")
344 {
345 socketValue = std::get_if<std::string>(&value);
346 if (socketValue == nullptr)
347 {
348 BMCWEB_LOG_ERROR
349 << "Socket property value is null";
350 }
351 }
352 }
353 }
354
355 if ((endpointValue != nullptr) &&
356 (socketValue != nullptr) &&
357 *endpointValue == conn.req.target())
358 {
359 endpointObjectPath = &objectPath.str;
360 break;
361 }
Ed Tanous168e20c2021-12-13 14:39:53 -0800362 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100363
Ed Tanous002d39b2022-05-31 08:59:27 -0700364 if (objects.empty() || endpointObjectPath == nullptr)
Ed Tanous168e20c2021-12-13 14:39:53 -0800365 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700366 BMCWEB_LOG_ERROR << "Cannot find requested EndpointId";
367 conn.close("Failed to match EndpointId");
Ed Tanous168e20c2021-12-13 14:39:53 -0800368 return;
369 }
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200370
Ed Tanous002d39b2022-05-31 08:59:27 -0700371 for (const auto& session : sessions)
372 {
373 if (session.second->getEndpointId() == conn.req.target())
Jason M. Bills613ea982022-02-16 14:24:30 -0800374 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700375 BMCWEB_LOG_ERROR
376 << "Cannot open new connection - socket is in use";
377 conn.close("Slot is in use");
Jason M. Bills613ea982022-02-16 14:24:30 -0800378 return;
379 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700380 }
Jason M. Bills613ea982022-02-16 14:24:30 -0800381
Ed Tanous002d39b2022-05-31 08:59:27 -0700382 // If the socket file exists (i.e. after bmcweb crash),
383 // we cannot reuse it.
384 std::remove((*socketValue).c_str());
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100385
Ed Tanous002d39b2022-05-31 08:59:27 -0700386 sessions[&conn] = std::make_shared<NbdProxyServer>(
387 conn, *socketValue, *endpointValue, *endpointObjectPath);
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200388
Ed Tanous002d39b2022-05-31 08:59:27 -0700389 sessions[&conn]->run();
390 };
391 crow::connections::systemBus->async_method_call(
392 std::move(openHandler), "xyz.openbmc_project.VirtualMedia",
393 "/xyz/openbmc_project/VirtualMedia",
394 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
Ed Tanous168e20c2021-12-13 14:39:53 -0800395 };
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100396
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200397 crow::connections::systemBus->async_method_call(
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100398 std::move(getUserInfoHandler),
399 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
400 "xyz.openbmc_project.User.Manager", "GetUserInfo",
401 conn.getUserName());
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200402 })
403 .onclose(
404 [](crow::websocket::Connection& conn, const std::string& reason) {
Ed Tanous002d39b2022-05-31 08:59:27 -0700405 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason << "')";
406 auto session = sessions.find(&conn);
407 if (session == sessions.end())
408 {
409 BMCWEB_LOG_DEBUG << "No session to close";
410 return;
411 }
412 session->second->close();
413 // Remove reference to session in global map
414 sessions.erase(session);
415 })
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200416 .onmessage([](crow::websocket::Connection& conn,
Vikram Bodireddyf5b16f02020-08-26 14:54:51 +0530417 const std::string& data, bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200418 BMCWEB_LOG_DEBUG << "nbd-proxy.onmessage(len = " << data.length()
419 << ")";
420 // Acquire proxy from sessions
421 auto session = sessions.find(&conn);
422 if (session != sessions.end())
423 {
424 if (session->second)
425 {
426 session->second->send(data);
427 return;
428 }
429 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200430 });
431}
432} // namespace nbd_proxy
433} // namespace crow