blob: ed5265d555cfc6bc3b246994389af267caafe498 [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
50 ~NbdProxyServer()
51 {
52 BMCWEB_LOG_DEBUG << "NbdProxyServer destructor";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020053 }
54
55 std::string getEndpointId() const
56 {
57 return endpointId;
58 }
59
60 void run()
61 {
62 acceptor.async_accept(
63 [this, self(shared_from_this())](boost::system::error_code ec,
64 stream_protocol::socket socket) {
65 if (ec)
66 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010067 BMCWEB_LOG_ERROR << "UNIX socket: async_accept error = "
68 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020069 return;
70 }
71 if (peerSocket)
72 {
73 // Something is wrong - socket shouldn't be acquired at this
74 // point
75 BMCWEB_LOG_ERROR
76 << "Failed to open connection - socket already used";
77 return;
78 }
79
80 BMCWEB_LOG_DEBUG << "Connection opened";
81 peerSocket = std::move(socket);
82 doRead();
83
84 // Trigger Write if any data was sent from server
85 // Initially this is negotiation chunk
86 doWrite();
87 });
88
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +020089 auto mountHandler = [this, self(shared_from_this())](
90 const boost::system::error_code ec,
Vikram Bodireddyf5b16f02020-08-26 14:54:51 +053091 const bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020092 if (ec)
93 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010094 BMCWEB_LOG_ERROR << "DBus error: cannot call mount method = "
95 << ec.message();
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +020096 connection.close("Failed to mount media");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020097 return;
98 }
99 };
100
101 crow::connections::systemBus->async_method_call(
102 std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path,
103 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount");
104 }
105
106 void send(const std::string_view data)
107 {
108 boost::asio::buffer_copy(ws2uxBuf.prepare(data.size()),
109 boost::asio::buffer(data));
110 ws2uxBuf.commit(data.size());
111 doWrite();
112 }
113
114 void close()
115 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100116 acceptor.close();
117 if (peerSocket)
118 {
119 BMCWEB_LOG_DEBUG << "peerSocket->close()";
120 peerSocket->close();
121 peerSocket.reset();
122 BMCWEB_LOG_DEBUG << "std::remove(" << socketId << ")";
123 std::remove(socketId.c_str());
124 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200125 // The reference to session should exists until unmount is
126 // called
127 auto unmountHandler = [](const boost::system::error_code ec) {
128 if (ec)
129 {
130 BMCWEB_LOG_ERROR << "DBus error: " << ec
131 << ", cannot call unmount method";
132 return;
133 }
134 };
135
136 crow::connections::systemBus->async_method_call(
137 std::move(unmountHandler), "xyz.openbmc_project.VirtualMedia", path,
138 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
139 }
140
141 private:
142 void doRead()
143 {
144 if (!peerSocket)
145 {
146 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
147 // Skip if UNIX socket is not created yet.
148 return;
149 }
150
151 // Trigger async read
152 peerSocket->async_read_some(
153 ux2wsBuf.prepare(nbdBufferSize),
154 [this, self(shared_from_this())](boost::system::error_code ec,
155 std::size_t bytesRead) {
156 if (ec)
157 {
158 BMCWEB_LOG_ERROR << "UNIX socket: async_read_some error = "
Iwona Winiarska123e8232019-11-29 12:34:33 +0100159 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200160 // UNIX socket has been closed by peer, best we can do is to
161 // break all connections
162 close();
163 return;
164 }
165
166 // Fetch data from UNIX socket
167
168 ux2wsBuf.commit(bytesRead);
169
170 // Paste it to WebSocket as binary
171 connection.sendBinary(
172 boost::beast::buffers_to_string(ux2wsBuf.data()));
173 ux2wsBuf.consume(bytesRead);
174
175 // Allow further reads
176 doRead();
177 });
178 }
179
180 void doWrite()
181 {
182 if (!peerSocket)
183 {
184 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
185 // Skip if UNIX socket is not created yet. Collect data, and wait
186 // for nbd-client connection
187 return;
188 }
189
190 if (uxWriteInProgress)
191 {
192 BMCWEB_LOG_ERROR << "Write in progress";
193 return;
194 }
195
Jason M. Bills9c0b4e52022-02-14 07:23:30 -0800196 if (ws2uxBuf.size() == 0)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200197 {
198 BMCWEB_LOG_ERROR << "No data to write to UNIX socket";
199 return;
200 }
201
202 uxWriteInProgress = true;
203 boost::asio::async_write(
204 *peerSocket, ws2uxBuf.data(),
205 [this, self(shared_from_this())](boost::system::error_code ec,
206 std::size_t bytesWritten) {
207 ws2uxBuf.consume(bytesWritten);
208 uxWriteInProgress = false;
209 if (ec)
210 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100211 BMCWEB_LOG_ERROR << "UNIX: async_write error = "
212 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200213 return;
214 }
215 // Retrigger doWrite if there is something in buffer
Iwona Winiarska123e8232019-11-29 12:34:33 +0100216 if (ws2uxBuf.size() > 0)
217 {
218 doWrite();
219 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200220 });
221 }
222
223 // Keeps UNIX socket endpoint file path
224 const std::string socketId;
225 const std::string endpointId;
226 const std::string path;
227
228 bool uxWriteInProgress = false;
229
230 // UNIX => WebSocket buffer
231 boost::beast::multi_buffer ux2wsBuf;
232
233 // WebSocket <= UNIX buffer
234 boost::beast::multi_buffer ws2uxBuf;
235
236 // Default acceptor for UNIX socket
237 stream_protocol::acceptor acceptor;
238
239 // The socket used to communicate with the client.
240 std::optional<stream_protocol::socket> peerSocket;
241
242 crow::websocket::Connection& connection;
243};
244
245static boost::container::flat_map<crow::websocket::Connection*,
246 std::shared_ptr<NbdProxyServer>>
247 sessions;
248
Ed Tanous81ce6092020-12-17 16:54:55 +0000249inline void requestRoutes(App& app)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200250{
251 BMCWEB_ROUTE(app, "/nbd/<str>")
252 .websocket()
Gunnar Millsccd584f2021-11-16 11:36:33 -0600253 .onopen([](crow::websocket::Connection& conn,
254 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200255 BMCWEB_LOG_DEBUG << "nbd-proxy.onopen(" << &conn << ")";
256
Ed Tanous168e20c2021-12-13 14:39:53 -0800257 auto getUserInfoHandler = [&conn, asyncResp](
258 const boost::system::error_code ec,
259 boost::container::flat_map<
260 std::string,
261 dbus::utility::DbusVariantType>
262 userInfo) {
263 if (ec)
264 {
265 BMCWEB_LOG_ERROR << "GetUserInfo failed...";
266 conn.close("Failed to get user information");
267 return;
268 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100269
Ed Tanous168e20c2021-12-13 14:39:53 -0800270 const std::string* userRolePtr = nullptr;
271 auto userInfoIter = userInfo.find("UserPrivilege");
272 if (userInfoIter != userInfo.end())
273 {
274 userRolePtr =
275 std::get_if<std::string>(&userInfoIter->second);
276 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100277
Ed Tanous168e20c2021-12-13 14:39:53 -0800278 std::string userRole{};
279 if (userRolePtr != nullptr)
280 {
281 userRole = *userRolePtr;
282 BMCWEB_LOG_DEBUG << "userName = " << conn.getUserName()
283 << " userRole = " << *userRolePtr;
284 }
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200285
Ed Tanous168e20c2021-12-13 14:39:53 -0800286 // Get the user privileges from the role
287 ::redfish::Privileges userPrivileges =
288 ::redfish::getUserPrivileges(userRole);
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200289
Ed Tanous168e20c2021-12-13 14:39:53 -0800290 const ::redfish::Privileges requiredPrivileges{
291 requiredPrivilegeString};
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200292
Ed Tanous168e20c2021-12-13 14:39:53 -0800293 if (!userPrivileges.isSupersetOf(requiredPrivileges))
294 {
295 BMCWEB_LOG_DEBUG << "User " << conn.getUserName()
296 << " not authorized for nbd connection";
297 conn.close("Unathourized access");
298 return;
299 }
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200300
Ed Tanous168e20c2021-12-13 14:39:53 -0800301 auto openHandler =
302 [&conn, asyncResp](
303 const boost::system::error_code ec,
304 const dbus::utility::ManagedObjectType& objects) {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200305 const std::string* socketValue = nullptr;
306 const std::string* endpointValue = nullptr;
307 const std::string* endpointObjectPath = nullptr;
308
309 if (ec)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100310 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200311 BMCWEB_LOG_ERROR << "DBus error: " << ec.message();
312 conn.close("Failed to create mount point");
313 return;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100314 }
315
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200316 for (const auto& objectPath : objects)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100317 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200318 const auto interfaceMap = objectPath.second.find(
319 "xyz.openbmc_project.VirtualMedia.MountPoint");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100320
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200321 if (interfaceMap == objectPath.second.end())
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100322 {
323 BMCWEB_LOG_DEBUG
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200324 << "Cannot find MountPoint object";
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100325 continue;
326 }
327
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200328 const auto endpoint =
329 interfaceMap->second.find("EndpointId");
330 if (endpoint == interfaceMap->second.end())
331 {
332 BMCWEB_LOG_DEBUG
333 << "Cannot find EndpointId property";
334 continue;
335 }
336
337 endpointValue =
338 std::get_if<std::string>(&endpoint->second);
339
340 if (endpointValue == nullptr)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100341 {
342 BMCWEB_LOG_ERROR
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200343 << "EndpointId property value is null";
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100344 continue;
345 }
346
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200347 if (*endpointValue == conn.req.target())
348 {
349 const auto socket =
350 interfaceMap->second.find("Socket");
351 if (socket == interfaceMap->second.end())
352 {
353 BMCWEB_LOG_DEBUG
354 << "Cannot find Socket property";
355 continue;
356 }
357
358 socketValue =
359 std::get_if<std::string>(&socket->second);
360 if (socketValue == nullptr)
361 {
362 BMCWEB_LOG_ERROR
363 << "Socket property value is null";
364 continue;
365 }
366
367 endpointObjectPath = &objectPath.first.str;
368 break;
369 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100370 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100371
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200372 if (objects.empty() || endpointObjectPath == nullptr)
373 {
374 BMCWEB_LOG_ERROR
375 << "Cannot find requested EndpointId";
376 conn.close("Failed to match EndpointId");
377 return;
378 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100379
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200380 for (const auto& session : sessions)
381 {
382 if (session.second->getEndpointId() ==
383 conn.req.target())
384 {
385 BMCWEB_LOG_ERROR
386 << "Cannot open new connection - socket is "
387 "in use";
388 conn.close("Slot is in use");
389 return;
390 }
391 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100392
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200393 // If the socket file exists (i.e. after bmcweb crash),
394 // we cannot reuse it.
395 std::remove((*socketValue).c_str());
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100396
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200397 sessions[&conn] = std::make_shared<NbdProxyServer>(
Ed Tanous81ce6092020-12-17 16:54:55 +0000398 conn, *socketValue, *endpointValue,
399 *endpointObjectPath);
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100400
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200401 sessions[&conn]->run();
402 };
Ed Tanous168e20c2021-12-13 14:39:53 -0800403 crow::connections::systemBus->async_method_call(
404 std::move(openHandler), "xyz.openbmc_project.VirtualMedia",
405 "/xyz/openbmc_project/VirtualMedia",
406 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
407 };
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100408
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200409 crow::connections::systemBus->async_method_call(
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100410 std::move(getUserInfoHandler),
411 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
412 "xyz.openbmc_project.User.Manager", "GetUserInfo",
413 conn.getUserName());
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200414 })
415 .onclose(
416 [](crow::websocket::Connection& conn, const std::string& reason) {
417 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason
418 << "')";
419 auto session = sessions.find(&conn);
420 if (session == sessions.end())
421 {
422 BMCWEB_LOG_DEBUG << "No session to close";
423 return;
424 }
Iwona Winiarska123e8232019-11-29 12:34:33 +0100425 // Remove reference to session in global map
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200426 sessions.erase(session);
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200427 session->second->close();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200428 })
429 .onmessage([](crow::websocket::Connection& conn,
Vikram Bodireddyf5b16f02020-08-26 14:54:51 +0530430 const std::string& data, bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200431 BMCWEB_LOG_DEBUG << "nbd-proxy.onmessage(len = " << data.length()
432 << ")";
433 // Acquire proxy from sessions
434 auto session = sessions.find(&conn);
435 if (session != sessions.end())
436 {
437 if (session->second)
438 {
439 session->second->send(data);
440 return;
441 }
442 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200443 });
444}
445} // namespace nbd_proxy
446} // namespace crow