blob: e0a011101ea02d5ca3708314d13791d5d02a0e26 [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 Tanous04e438c2020-10-03 08:06:26 -070017#include <app.hpp>
Ed Tanousd43cd0c2020-09-30 20:46:53 -070018#include <boost/asio/buffer.hpp>
19#include <boost/asio/local/stream_protocol.hpp>
20#include <boost/asio/write.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020021#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>
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010025#include <privileges.hpp>
Ed Tanous04e438c2020-10-03 08:06:26 -070026#include <websocket.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020027
28namespace crow
29{
30
31namespace nbd_proxy
32{
33
34using boost::asio::local::stream_protocol;
35
36static constexpr auto nbdBufferSize = 131088;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010037static const char* requiredPrivilegeString = "ConfigureManager";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020038
39struct NbdProxyServer : std::enable_shared_from_this<NbdProxyServer>
40{
41 NbdProxyServer(crow::websocket::Connection& connIn,
42 const std::string& socketIdIn,
43 const std::string& endpointIdIn, const std::string& pathIn) :
44 socketId(socketIdIn),
45 endpointId(endpointIdIn), path(pathIn),
Ed Tanous2c70f802020-09-28 14:29:23 -070046 acceptor(connIn.getIoContext(), stream_protocol::endpoint(socketId)),
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020047 connection(connIn)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050048 {}
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020049
Ed Tanous08e9fa92022-02-16 16:18:08 -080050 NbdProxyServer(const NbdProxyServer&) = delete;
51 NbdProxyServer(NbdProxyServer&&) = delete;
52 NbdProxyServer& operator=(const NbdProxyServer&) = delete;
53 NbdProxyServer& operator=(NbdProxyServer&&) = delete;
54
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020055 ~NbdProxyServer()
56 {
57 BMCWEB_LOG_DEBUG << "NbdProxyServer destructor";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020058 }
59
60 std::string getEndpointId() const
61 {
62 return endpointId;
63 }
64
65 void run()
66 {
67 acceptor.async_accept(
68 [this, self(shared_from_this())](boost::system::error_code ec,
69 stream_protocol::socket socket) {
70 if (ec)
71 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010072 BMCWEB_LOG_ERROR << "UNIX socket: async_accept error = "
73 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020074 return;
75 }
76 if (peerSocket)
77 {
78 // Something is wrong - socket shouldn't be acquired at this
79 // point
80 BMCWEB_LOG_ERROR
81 << "Failed to open connection - socket already used";
82 return;
83 }
84
85 BMCWEB_LOG_DEBUG << "Connection opened";
86 peerSocket = std::move(socket);
87 doRead();
88
89 // Trigger Write if any data was sent from server
90 // Initially this is negotiation chunk
91 doWrite();
92 });
93
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +020094 auto mountHandler = [this, self(shared_from_this())](
95 const boost::system::error_code ec,
Vikram Bodireddyf5b16f02020-08-26 14:54:51 +053096 const bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020097 if (ec)
98 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010099 BMCWEB_LOG_ERROR << "DBus error: cannot call mount method = "
100 << ec.message();
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200101 connection.close("Failed to mount media");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200102 return;
103 }
104 };
105
106 crow::connections::systemBus->async_method_call(
107 std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path,
108 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount");
109 }
110
111 void send(const std::string_view data)
112 {
113 boost::asio::buffer_copy(ws2uxBuf.prepare(data.size()),
114 boost::asio::buffer(data));
115 ws2uxBuf.commit(data.size());
116 doWrite();
117 }
118
119 void close()
120 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100121 acceptor.close();
122 if (peerSocket)
123 {
124 BMCWEB_LOG_DEBUG << "peerSocket->close()";
125 peerSocket->close();
126 peerSocket.reset();
127 BMCWEB_LOG_DEBUG << "std::remove(" << socketId << ")";
128 std::remove(socketId.c_str());
129 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200130 // The reference to session should exists until unmount is
131 // called
132 auto unmountHandler = [](const boost::system::error_code ec) {
133 if (ec)
134 {
135 BMCWEB_LOG_ERROR << "DBus error: " << ec
136 << ", cannot call unmount method";
137 return;
138 }
139 };
140
141 crow::connections::systemBus->async_method_call(
142 std::move(unmountHandler), "xyz.openbmc_project.VirtualMedia", path,
143 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
144 }
145
146 private:
147 void doRead()
148 {
149 if (!peerSocket)
150 {
151 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
152 // Skip if UNIX socket is not created yet.
153 return;
154 }
155
156 // Trigger async read
157 peerSocket->async_read_some(
158 ux2wsBuf.prepare(nbdBufferSize),
159 [this, self(shared_from_this())](boost::system::error_code ec,
160 std::size_t bytesRead) {
161 if (ec)
162 {
163 BMCWEB_LOG_ERROR << "UNIX socket: async_read_some error = "
Iwona Winiarska123e8232019-11-29 12:34:33 +0100164 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200165 // UNIX socket has been closed by peer, best we can do is to
166 // break all connections
167 close();
168 return;
169 }
170
171 // Fetch data from UNIX socket
172
173 ux2wsBuf.commit(bytesRead);
174
175 // Paste it to WebSocket as binary
176 connection.sendBinary(
177 boost::beast::buffers_to_string(ux2wsBuf.data()));
178 ux2wsBuf.consume(bytesRead);
179
180 // Allow further reads
181 doRead();
182 });
183 }
184
185 void doWrite()
186 {
187 if (!peerSocket)
188 {
189 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
190 // Skip if UNIX socket is not created yet. Collect data, and wait
191 // for nbd-client connection
192 return;
193 }
194
195 if (uxWriteInProgress)
196 {
197 BMCWEB_LOG_ERROR << "Write in progress";
198 return;
199 }
200
Jason M. Bills9c0b4e52022-02-14 07:23:30 -0800201 if (ws2uxBuf.size() == 0)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200202 {
203 BMCWEB_LOG_ERROR << "No data to write to UNIX socket";
204 return;
205 }
206
207 uxWriteInProgress = true;
208 boost::asio::async_write(
209 *peerSocket, ws2uxBuf.data(),
210 [this, self(shared_from_this())](boost::system::error_code ec,
211 std::size_t bytesWritten) {
212 ws2uxBuf.consume(bytesWritten);
213 uxWriteInProgress = false;
214 if (ec)
215 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100216 BMCWEB_LOG_ERROR << "UNIX: async_write error = "
217 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200218 return;
219 }
220 // Retrigger doWrite if there is something in buffer
Iwona Winiarska123e8232019-11-29 12:34:33 +0100221 if (ws2uxBuf.size() > 0)
222 {
223 doWrite();
224 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200225 });
226 }
227
228 // Keeps UNIX socket endpoint file path
229 const std::string socketId;
230 const std::string endpointId;
231 const std::string path;
232
233 bool uxWriteInProgress = false;
234
235 // UNIX => WebSocket buffer
236 boost::beast::multi_buffer ux2wsBuf;
237
238 // WebSocket <= UNIX buffer
239 boost::beast::multi_buffer ws2uxBuf;
240
241 // Default acceptor for UNIX socket
242 stream_protocol::acceptor acceptor;
243
244 // The socket used to communicate with the client.
245 std::optional<stream_protocol::socket> peerSocket;
246
247 crow::websocket::Connection& connection;
248};
249
250static boost::container::flat_map<crow::websocket::Connection*,
251 std::shared_ptr<NbdProxyServer>>
252 sessions;
253
Ed Tanous81ce6092020-12-17 16:54:55 +0000254inline void requestRoutes(App& app)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200255{
256 BMCWEB_ROUTE(app, "/nbd/<str>")
257 .websocket()
Gunnar Millsccd584f2021-11-16 11:36:33 -0600258 .onopen([](crow::websocket::Connection& conn,
259 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200260 BMCWEB_LOG_DEBUG << "nbd-proxy.onopen(" << &conn << ")";
261
Ed Tanous168e20c2021-12-13 14:39:53 -0800262 auto getUserInfoHandler = [&conn, asyncResp](
263 const boost::system::error_code ec,
Ed Tanousb9d36b42022-02-26 21:42:46 -0800264 const dbus::utilities::
265 DBusPropertiesMap& userInfo) {
Ed Tanous168e20c2021-12-13 14:39:53 -0800266 if (ec)
267 {
268 BMCWEB_LOG_ERROR << "GetUserInfo failed...";
269 conn.close("Failed to get user information");
270 return;
271 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100272
Ed Tanous168e20c2021-12-13 14:39:53 -0800273 const std::string* userRolePtr = nullptr;
274 auto userInfoIter = userInfo.find("UserPrivilege");
275 if (userInfoIter != userInfo.end())
276 {
277 userRolePtr =
278 std::get_if<std::string>(&userInfoIter->second);
279 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100280
Ed Tanous168e20c2021-12-13 14:39:53 -0800281 std::string userRole{};
282 if (userRolePtr != nullptr)
283 {
284 userRole = *userRolePtr;
285 BMCWEB_LOG_DEBUG << "userName = " << conn.getUserName()
286 << " userRole = " << *userRolePtr;
287 }
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200288
Ed Tanous168e20c2021-12-13 14:39:53 -0800289 // Get the user privileges from the role
290 ::redfish::Privileges userPrivileges =
291 ::redfish::getUserPrivileges(userRole);
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200292
Ed Tanous168e20c2021-12-13 14:39:53 -0800293 const ::redfish::Privileges requiredPrivileges{
294 requiredPrivilegeString};
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200295
Ed Tanous168e20c2021-12-13 14:39:53 -0800296 if (!userPrivileges.isSupersetOf(requiredPrivileges))
297 {
298 BMCWEB_LOG_DEBUG << "User " << conn.getUserName()
299 << " not authorized for nbd connection";
300 conn.close("Unathourized access");
301 return;
302 }
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200303
Jason M. Bills613ea982022-02-16 14:24:30 -0800304 auto openHandler = [&conn, asyncResp](
305 const boost::system::error_code ec,
306 const dbus::utility::ManagedObjectType&
307 objects) {
308 const std::string* socketValue = nullptr;
309 const std::string* endpointValue = nullptr;
310 const std::string* endpointObjectPath = nullptr;
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200311
Jason M. Bills613ea982022-02-16 14:24:30 -0800312 if (ec)
313 {
314 BMCWEB_LOG_ERROR << "DBus error: " << ec.message();
315 conn.close("Failed to create mount point");
316 return;
317 }
318
319 for (const auto& [objectPath, interfaces] : objects)
320 {
321 for (const auto& [interface, properties] : interfaces)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100322 {
Jason M. Bills613ea982022-02-16 14:24:30 -0800323 if (interface !=
324 "xyz.openbmc_project.VirtualMedia.MountPoint")
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100325 {
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100326 continue;
327 }
328
Jason M. Bills613ea982022-02-16 14:24:30 -0800329 for (const auto& [name, value] : properties)
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200330 {
Jason M. Bills613ea982022-02-16 14:24:30 -0800331 if (name == "EndpointId")
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200332 {
Jason M. Bills613ea982022-02-16 14:24:30 -0800333 endpointValue =
334 std::get_if<std::string>(&value);
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200335
Jason M. Bills613ea982022-02-16 14:24:30 -0800336 if (endpointValue == nullptr)
337 {
338 BMCWEB_LOG_ERROR
339 << "EndpointId property value is null";
340 }
341 }
342 if (name == "Socket")
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200343 {
Jason M. Bills613ea982022-02-16 14:24:30 -0800344 socketValue =
345 std::get_if<std::string>(&value);
346 if (socketValue == nullptr)
347 {
348 BMCWEB_LOG_ERROR
349 << "Socket property value is null";
350 }
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200351 }
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200352 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100353 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100354
Ed Tanous08e9fa92022-02-16 16:18:08 -0800355 if ((endpointValue != nullptr) &&
356 (socketValue != nullptr) &&
Jason M. Bills613ea982022-02-16 14:24:30 -0800357 *endpointValue == conn.req.target())
358 {
359 endpointObjectPath = &objectPath.str;
360 break;
361 }
362 }
363
364 if (objects.empty() || endpointObjectPath == nullptr)
365 {
366 BMCWEB_LOG_ERROR << "Cannot find requested EndpointId";
367 conn.close("Failed to match EndpointId");
368 return;
369 }
370
371 for (const auto& session : sessions)
372 {
373 if (session.second->getEndpointId() ==
374 conn.req.target())
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200375 {
376 BMCWEB_LOG_ERROR
Jason M. Bills613ea982022-02-16 14:24:30 -0800377 << "Cannot open new connection - socket is in use";
378 conn.close("Slot is in use");
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200379 return;
380 }
Jason M. Bills613ea982022-02-16 14:24:30 -0800381 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100382
Jason M. Bills613ea982022-02-16 14:24:30 -0800383 // If the socket file exists (i.e. after bmcweb crash),
384 // we cannot reuse it.
385 std::remove((*socketValue).c_str());
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100386
Jason M. Bills613ea982022-02-16 14:24:30 -0800387 sessions[&conn] = std::make_shared<NbdProxyServer>(
388 conn, *socketValue, *endpointValue,
389 *endpointObjectPath);
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100390
Jason M. Bills613ea982022-02-16 14:24:30 -0800391 sessions[&conn]->run();
392 };
Ed Tanous168e20c2021-12-13 14:39:53 -0800393 crow::connections::systemBus->async_method_call(
394 std::move(openHandler), "xyz.openbmc_project.VirtualMedia",
395 "/xyz/openbmc_project/VirtualMedia",
396 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
397 };
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100398
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200399 crow::connections::systemBus->async_method_call(
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100400 std::move(getUserInfoHandler),
401 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
402 "xyz.openbmc_project.User.Manager", "GetUserInfo",
403 conn.getUserName());
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200404 })
405 .onclose(
406 [](crow::websocket::Connection& conn, const std::string& reason) {
407 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason
408 << "')";
409 auto session = sessions.find(&conn);
410 if (session == sessions.end())
411 {
412 BMCWEB_LOG_DEBUG << "No session to close";
413 return;
414 }
Iwona Winiarska123e8232019-11-29 12:34:33 +0100415 // Remove reference to session in global map
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200416 sessions.erase(session);
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200417 session->second->close();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200418 })
419 .onmessage([](crow::websocket::Connection& conn,
Vikram Bodireddyf5b16f02020-08-26 14:54:51 +0530420 const std::string& data, bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200421 BMCWEB_LOG_DEBUG << "nbd-proxy.onmessage(len = " << data.length()
422 << ")";
423 // Acquire proxy from sessions
424 auto session = sessions.find(&conn);
425 if (session != sessions.end())
426 {
427 if (session->second)
428 {
429 session->second->send(data);
430 return;
431 }
432 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200433 });
434}
435} // namespace nbd_proxy
436} // namespace crow