blob: 85fb2d467e8b3dadcb5587dac9b021cf6df5a91d [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
Gunnar Mills1214b7e2020-06-04 10:11:30 -050028#include <variant>
29
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020030namespace 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),
Ed Tanous2c70f802020-09-28 14:29:23 -070048 acceptor(connIn.getIoContext(), stream_protocol::endpoint(socketId)),
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020049 connection(connIn)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050050 {}
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020051
52 ~NbdProxyServer()
53 {
54 BMCWEB_LOG_DEBUG << "NbdProxyServer destructor";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020055 }
56
57 std::string getEndpointId() const
58 {
59 return endpointId;
60 }
61
62 void run()
63 {
64 acceptor.async_accept(
65 [this, self(shared_from_this())](boost::system::error_code ec,
66 stream_protocol::socket socket) {
67 if (ec)
68 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010069 BMCWEB_LOG_ERROR << "UNIX socket: async_accept error = "
70 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020071 return;
72 }
73 if (peerSocket)
74 {
75 // Something is wrong - socket shouldn't be acquired at this
76 // point
77 BMCWEB_LOG_ERROR
78 << "Failed to open connection - socket already used";
79 return;
80 }
81
82 BMCWEB_LOG_DEBUG << "Connection opened";
83 peerSocket = std::move(socket);
84 doRead();
85
86 // Trigger Write if any data was sent from server
87 // Initially this is negotiation chunk
88 doWrite();
89 });
90
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +020091 auto mountHandler = [this, self(shared_from_this())](
92 const boost::system::error_code ec,
Vikram Bodireddyf5b16f02020-08-26 14:54:51 +053093 const bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020094 if (ec)
95 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010096 BMCWEB_LOG_ERROR << "DBus error: cannot call mount method = "
97 << ec.message();
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +020098 connection.close("Failed to mount media");
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
Ed Tanous81ce6092020-12-17 16:54:55 +0000251inline void requestRoutes(App& app)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200252{
253 BMCWEB_ROUTE(app, "/nbd/<str>")
254 .websocket()
zhanghch050f3d3a02021-10-21 14:07:57 +0800255 .onopen([](crow::websocket::Connection& conn) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200256 BMCWEB_LOG_DEBUG << "nbd-proxy.onopen(" << &conn << ")";
257
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200258 auto getUserInfoHandler =
zhanghch050f3d3a02021-10-21 14:07:57 +0800259 [&conn](const boost::system::error_code ec,
260 boost::container::flat_map<
261 std::string, std::variant<bool, std::string,
262 std::vector<std::string>>>
263 userInfo) {
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100264 if (ec)
265 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200266 BMCWEB_LOG_ERROR << "GetUserInfo failed...";
267 conn.close("Failed to get user information");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100268 return;
269 }
270
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200271 const std::string* userRolePtr = nullptr;
272 auto userInfoIter = userInfo.find("UserPrivilege");
273 if (userInfoIter != userInfo.end())
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100274 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200275 userRolePtr =
276 std::get_if<std::string>(&userInfoIter->second);
277 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100278
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200279 std::string userRole{};
280 if (userRolePtr != nullptr)
281 {
282 userRole = *userRolePtr;
283 BMCWEB_LOG_DEBUG << "userName = " << conn.getUserName()
284 << " userRole = " << *userRolePtr;
285 }
286
287 // Get the user privileges from the role
288 ::redfish::Privileges userPrivileges =
289 ::redfish::getUserPrivileges(userRole);
290
291 const ::redfish::Privileges requiredPrivileges{
292 requiredPrivilegeString};
293
294 if (!userPrivileges.isSupersetOf(requiredPrivileges))
295 {
296 BMCWEB_LOG_DEBUG
297 << "User " << conn.getUserName()
298 << " not authorized for nbd connection";
299 conn.close("Unathourized access");
300 return;
301 }
302
zhanghch050f3d3a02021-10-21 14:07:57 +0800303 auto openHandler = [&conn](
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200304 const boost::system::error_code ec,
305 const dbus::utility::
306 ManagedObjectType& objects) {
307 const std::string* socketValue = nullptr;
308 const std::string* endpointValue = nullptr;
309 const std::string* endpointObjectPath = nullptr;
310
311 if (ec)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100312 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200313 BMCWEB_LOG_ERROR << "DBus error: " << ec.message();
314 conn.close("Failed to create mount point");
315 return;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100316 }
317
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200318 for (const auto& objectPath : objects)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100319 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200320 const auto interfaceMap = objectPath.second.find(
321 "xyz.openbmc_project.VirtualMedia.MountPoint");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100322
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200323 if (interfaceMap == objectPath.second.end())
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100324 {
325 BMCWEB_LOG_DEBUG
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200326 << "Cannot find MountPoint object";
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100327 continue;
328 }
329
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200330 const auto endpoint =
331 interfaceMap->second.find("EndpointId");
332 if (endpoint == interfaceMap->second.end())
333 {
334 BMCWEB_LOG_DEBUG
335 << "Cannot find EndpointId property";
336 continue;
337 }
338
339 endpointValue =
340 std::get_if<std::string>(&endpoint->second);
341
342 if (endpointValue == nullptr)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100343 {
344 BMCWEB_LOG_ERROR
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200345 << "EndpointId property value is null";
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100346 continue;
347 }
348
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200349 if (*endpointValue == conn.req.target())
350 {
351 const auto socket =
352 interfaceMap->second.find("Socket");
353 if (socket == interfaceMap->second.end())
354 {
355 BMCWEB_LOG_DEBUG
356 << "Cannot find Socket property";
357 continue;
358 }
359
360 socketValue =
361 std::get_if<std::string>(&socket->second);
362 if (socketValue == nullptr)
363 {
364 BMCWEB_LOG_ERROR
365 << "Socket property value is null";
366 continue;
367 }
368
369 endpointObjectPath = &objectPath.first.str;
370 break;
371 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100372 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100373
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200374 if (objects.empty() || endpointObjectPath == nullptr)
375 {
376 BMCWEB_LOG_ERROR
377 << "Cannot find requested EndpointId";
378 conn.close("Failed to match EndpointId");
379 return;
380 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100381
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200382 for (const auto& session : sessions)
383 {
384 if (session.second->getEndpointId() ==
385 conn.req.target())
386 {
387 BMCWEB_LOG_ERROR
388 << "Cannot open new connection - socket is "
389 "in use";
390 conn.close("Slot is in use");
391 return;
392 }
393 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100394
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200395 // If the socket file exists (i.e. after bmcweb crash),
396 // we cannot reuse it.
397 std::remove((*socketValue).c_str());
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100398
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200399 sessions[&conn] = std::make_shared<NbdProxyServer>(
Ed Tanous81ce6092020-12-17 16:54:55 +0000400 conn, *socketValue, *endpointValue,
401 *endpointObjectPath);
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100402
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200403 sessions[&conn]->run();
404 };
405 crow::connections::systemBus->async_method_call(
406 std::move(openHandler),
407 "xyz.openbmc_project.VirtualMedia",
408 "/xyz/openbmc_project/VirtualMedia",
409 "org.freedesktop.DBus.ObjectManager",
410 "GetManagedObjects");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100411 };
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100412
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200413 crow::connections::systemBus->async_method_call(
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100414 std::move(getUserInfoHandler),
415 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
416 "xyz.openbmc_project.User.Manager", "GetUserInfo",
417 conn.getUserName());
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200418 })
419 .onclose(
420 [](crow::websocket::Connection& conn, const std::string& reason) {
421 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason
422 << "')";
423 auto session = sessions.find(&conn);
424 if (session == sessions.end())
425 {
426 BMCWEB_LOG_DEBUG << "No session to close";
427 return;
428 }
Iwona Winiarska123e8232019-11-29 12:34:33 +0100429 // Remove reference to session in global map
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200430 sessions.erase(session);
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200431 session->second->close();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200432 })
433 .onmessage([](crow::websocket::Connection& conn,
Vikram Bodireddyf5b16f02020-08-26 14:54:51 +0530434 const std::string& data, bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200435 BMCWEB_LOG_DEBUG << "nbd-proxy.onmessage(len = " << data.length()
436 << ")";
437 // Acquire proxy from sessions
438 auto session = sessions.find(&conn);
439 if (session != sessions.end())
440 {
441 if (session->second)
442 {
443 session->second->send(data);
444 return;
445 }
446 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200447 });
448}
449} // namespace nbd_proxy
450} // namespace crow