blob: e2ebba1e2a93dbbb9e38828cfbdd8e375850f858 [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
17#include <app.h>
18#include <websocket.h>
19
Ed Tanousd43cd0c2020-09-30 20:46:53 -070020#include <boost/asio/buffer.hpp>
21#include <boost/asio/local/stream_protocol.hpp>
22#include <boost/asio/write.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020023#include <boost/beast/core/buffers_to_string.hpp>
24#include <boost/beast/core/multi_buffer.hpp>
25#include <boost/container/flat_map.hpp>
26#include <dbus_utility.hpp>
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010027#include <privileges.hpp>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020028
Gunnar Mills1214b7e2020-06-04 10:11:30 -050029#include <variant>
30
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020031namespace crow
32{
33
34namespace nbd_proxy
35{
36
37using boost::asio::local::stream_protocol;
38
39static constexpr auto nbdBufferSize = 131088;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010040static const char* requiredPrivilegeString = "ConfigureManager";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020041
42struct NbdProxyServer : std::enable_shared_from_this<NbdProxyServer>
43{
44 NbdProxyServer(crow::websocket::Connection& connIn,
45 const std::string& socketIdIn,
46 const std::string& endpointIdIn, const std::string& pathIn) :
47 socketId(socketIdIn),
48 endpointId(endpointIdIn), path(pathIn),
Ed Tanous2c70f802020-09-28 14:29:23 -070049 acceptor(connIn.getIoContext(), stream_protocol::endpoint(socketId)),
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020050 connection(connIn)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050051 {}
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020052
53 ~NbdProxyServer()
54 {
55 BMCWEB_LOG_DEBUG << "NbdProxyServer destructor";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020056 }
57
58 std::string getEndpointId() const
59 {
60 return endpointId;
61 }
62
63 void run()
64 {
65 acceptor.async_accept(
66 [this, self(shared_from_this())](boost::system::error_code ec,
67 stream_protocol::socket socket) {
68 if (ec)
69 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010070 BMCWEB_LOG_ERROR << "UNIX socket: async_accept error = "
71 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020072 return;
73 }
74 if (peerSocket)
75 {
76 // Something is wrong - socket shouldn't be acquired at this
77 // point
78 BMCWEB_LOG_ERROR
79 << "Failed to open connection - socket already used";
80 return;
81 }
82
83 BMCWEB_LOG_DEBUG << "Connection opened";
84 peerSocket = std::move(socket);
85 doRead();
86
87 // Trigger Write if any data was sent from server
88 // Initially this is negotiation chunk
89 doWrite();
90 });
91
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +020092 auto mountHandler = [this, self(shared_from_this())](
93 const boost::system::error_code ec,
Vikram Bodireddyf5b16f02020-08-26 14:54:51 +053094 const bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020095 if (ec)
96 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010097 BMCWEB_LOG_ERROR << "DBus error: cannot call mount method = "
98 << ec.message();
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +020099 connection.close("Failed to mount media");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200100 return;
101 }
102 };
103
104 crow::connections::systemBus->async_method_call(
105 std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path,
106 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount");
107 }
108
109 void send(const std::string_view data)
110 {
111 boost::asio::buffer_copy(ws2uxBuf.prepare(data.size()),
112 boost::asio::buffer(data));
113 ws2uxBuf.commit(data.size());
114 doWrite();
115 }
116
117 void close()
118 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100119 acceptor.close();
120 if (peerSocket)
121 {
122 BMCWEB_LOG_DEBUG << "peerSocket->close()";
123 peerSocket->close();
124 peerSocket.reset();
125 BMCWEB_LOG_DEBUG << "std::remove(" << socketId << ")";
126 std::remove(socketId.c_str());
127 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200128 // The reference to session should exists until unmount is
129 // called
130 auto unmountHandler = [](const boost::system::error_code ec) {
131 if (ec)
132 {
133 BMCWEB_LOG_ERROR << "DBus error: " << ec
134 << ", cannot call unmount method";
135 return;
136 }
137 };
138
139 crow::connections::systemBus->async_method_call(
140 std::move(unmountHandler), "xyz.openbmc_project.VirtualMedia", path,
141 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
142 }
143
144 private:
145 void doRead()
146 {
147 if (!peerSocket)
148 {
149 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
150 // Skip if UNIX socket is not created yet.
151 return;
152 }
153
154 // Trigger async read
155 peerSocket->async_read_some(
156 ux2wsBuf.prepare(nbdBufferSize),
157 [this, self(shared_from_this())](boost::system::error_code ec,
158 std::size_t bytesRead) {
159 if (ec)
160 {
161 BMCWEB_LOG_ERROR << "UNIX socket: async_read_some error = "
Iwona Winiarska123e8232019-11-29 12:34:33 +0100162 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200163 // UNIX socket has been closed by peer, best we can do is to
164 // break all connections
165 close();
166 return;
167 }
168
169 // Fetch data from UNIX socket
170
171 ux2wsBuf.commit(bytesRead);
172
173 // Paste it to WebSocket as binary
174 connection.sendBinary(
175 boost::beast::buffers_to_string(ux2wsBuf.data()));
176 ux2wsBuf.consume(bytesRead);
177
178 // Allow further reads
179 doRead();
180 });
181 }
182
183 void doWrite()
184 {
185 if (!peerSocket)
186 {
187 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
188 // Skip if UNIX socket is not created yet. Collect data, and wait
189 // for nbd-client connection
190 return;
191 }
192
193 if (uxWriteInProgress)
194 {
195 BMCWEB_LOG_ERROR << "Write in progress";
196 return;
197 }
198
199 if (ws2uxBuf.size() == 0)
200 {
201 BMCWEB_LOG_ERROR << "No data to write to UNIX socket";
202 return;
203 }
204
205 uxWriteInProgress = true;
206 boost::asio::async_write(
207 *peerSocket, ws2uxBuf.data(),
208 [this, self(shared_from_this())](boost::system::error_code ec,
209 std::size_t bytesWritten) {
210 ws2uxBuf.consume(bytesWritten);
211 uxWriteInProgress = false;
212 if (ec)
213 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100214 BMCWEB_LOG_ERROR << "UNIX: async_write error = "
215 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200216 return;
217 }
218 // Retrigger doWrite if there is something in buffer
Iwona Winiarska123e8232019-11-29 12:34:33 +0100219 if (ws2uxBuf.size() > 0)
220 {
221 doWrite();
222 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200223 });
224 }
225
226 // Keeps UNIX socket endpoint file path
227 const std::string socketId;
228 const std::string endpointId;
229 const std::string path;
230
231 bool uxWriteInProgress = false;
232
233 // UNIX => WebSocket buffer
234 boost::beast::multi_buffer ux2wsBuf;
235
236 // WebSocket <= UNIX buffer
237 boost::beast::multi_buffer ws2uxBuf;
238
239 // Default acceptor for UNIX socket
240 stream_protocol::acceptor acceptor;
241
242 // The socket used to communicate with the client.
243 std::optional<stream_protocol::socket> peerSocket;
244
245 crow::websocket::Connection& connection;
246};
247
248static boost::container::flat_map<crow::websocket::Connection*,
249 std::shared_ptr<NbdProxyServer>>
250 sessions;
251
Ed Tanous52cc1122020-07-18 13:51:21 -0700252void requestRoutes(App& app)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200253{
254 BMCWEB_ROUTE(app, "/nbd/<str>")
255 .websocket()
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100256 .onopen([](crow::websocket::Connection& conn,
257 std::shared_ptr<bmcweb::AsyncResp> asyncResp) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200258 BMCWEB_LOG_DEBUG << "nbd-proxy.onopen(" << &conn << ")";
259
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200260 auto getUserInfoHandler =
261 [&conn, asyncResp](
262 const boost::system::error_code ec,
263 boost::container::flat_map<
264 std::string, std::variant<bool, std::string,
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100265 std::vector<std::string>>>
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200266 userInfo) {
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100267 if (ec)
268 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200269 BMCWEB_LOG_ERROR << "GetUserInfo failed...";
270 conn.close("Failed to get user information");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100271 return;
272 }
273
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200274 const std::string* userRolePtr = nullptr;
275 auto userInfoIter = userInfo.find("UserPrivilege");
276 if (userInfoIter != userInfo.end())
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100277 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200278 userRolePtr =
279 std::get_if<std::string>(&userInfoIter->second);
280 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100281
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200282 std::string userRole{};
283 if (userRolePtr != nullptr)
284 {
285 userRole = *userRolePtr;
286 BMCWEB_LOG_DEBUG << "userName = " << conn.getUserName()
287 << " userRole = " << *userRolePtr;
288 }
289
290 // Get the user privileges from the role
291 ::redfish::Privileges userPrivileges =
292 ::redfish::getUserPrivileges(userRole);
293
294 const ::redfish::Privileges requiredPrivileges{
295 requiredPrivilegeString};
296
297 if (!userPrivileges.isSupersetOf(requiredPrivileges))
298 {
299 BMCWEB_LOG_DEBUG
300 << "User " << conn.getUserName()
301 << " not authorized for nbd connection";
302 conn.close("Unathourized access");
303 return;
304 }
305
306 auto openHandler = [&conn, asyncResp](
307 const boost::system::error_code ec,
308 const dbus::utility::
309 ManagedObjectType& objects) {
310 const std::string* socketValue = nullptr;
311 const std::string* endpointValue = nullptr;
312 const std::string* endpointObjectPath = nullptr;
313
314 if (ec)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100315 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200316 BMCWEB_LOG_ERROR << "DBus error: " << ec.message();
317 conn.close("Failed to create mount point");
318 return;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100319 }
320
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200321 for (const auto& objectPath : objects)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100322 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200323 const auto interfaceMap = objectPath.second.find(
324 "xyz.openbmc_project.VirtualMedia.MountPoint");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100325
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200326 if (interfaceMap == objectPath.second.end())
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100327 {
328 BMCWEB_LOG_DEBUG
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200329 << "Cannot find MountPoint object";
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100330 continue;
331 }
332
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200333 const auto endpoint =
334 interfaceMap->second.find("EndpointId");
335 if (endpoint == interfaceMap->second.end())
336 {
337 BMCWEB_LOG_DEBUG
338 << "Cannot find EndpointId property";
339 continue;
340 }
341
342 endpointValue =
343 std::get_if<std::string>(&endpoint->second);
344
345 if (endpointValue == nullptr)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100346 {
347 BMCWEB_LOG_ERROR
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200348 << "EndpointId property value is null";
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100349 continue;
350 }
351
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200352 if (*endpointValue == conn.req.target())
353 {
354 const auto socket =
355 interfaceMap->second.find("Socket");
356 if (socket == interfaceMap->second.end())
357 {
358 BMCWEB_LOG_DEBUG
359 << "Cannot find Socket property";
360 continue;
361 }
362
363 socketValue =
364 std::get_if<std::string>(&socket->second);
365 if (socketValue == nullptr)
366 {
367 BMCWEB_LOG_ERROR
368 << "Socket property value is null";
369 continue;
370 }
371
372 endpointObjectPath = &objectPath.first.str;
373 break;
374 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100375 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100376
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200377 if (objects.empty() || endpointObjectPath == nullptr)
378 {
379 BMCWEB_LOG_ERROR
380 << "Cannot find requested EndpointId";
381 conn.close("Failed to match EndpointId");
382 return;
383 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100384
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200385 for (const auto& session : sessions)
386 {
387 if (session.second->getEndpointId() ==
388 conn.req.target())
389 {
390 BMCWEB_LOG_ERROR
391 << "Cannot open new connection - socket is "
392 "in use";
393 conn.close("Slot is in use");
394 return;
395 }
396 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100397
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200398 // If the socket file exists (i.e. after bmcweb crash),
399 // we cannot reuse it.
400 std::remove((*socketValue).c_str());
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100401
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200402 sessions[&conn] = std::make_shared<NbdProxyServer>(
403 conn, std::move(*socketValue),
404 std::move(*endpointValue),
405 std::move(*endpointObjectPath));
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100406
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200407 sessions[&conn]->run();
408 };
409 crow::connections::systemBus->async_method_call(
410 std::move(openHandler),
411 "xyz.openbmc_project.VirtualMedia",
412 "/xyz/openbmc_project/VirtualMedia",
413 "org.freedesktop.DBus.ObjectManager",
414 "GetManagedObjects");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100415 };
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100416
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200417 crow::connections::systemBus->async_method_call(
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100418 std::move(getUserInfoHandler),
419 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
420 "xyz.openbmc_project.User.Manager", "GetUserInfo",
421 conn.getUserName());
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200422 })
423 .onclose(
424 [](crow::websocket::Connection& conn, const std::string& reason) {
425 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason
426 << "')";
427 auto session = sessions.find(&conn);
428 if (session == sessions.end())
429 {
430 BMCWEB_LOG_DEBUG << "No session to close";
431 return;
432 }
Iwona Winiarska123e8232019-11-29 12:34:33 +0100433 // Remove reference to session in global map
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200434 sessions.erase(session);
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200435 session->second->close();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200436 })
437 .onmessage([](crow::websocket::Connection& conn,
Vikram Bodireddyf5b16f02020-08-26 14:54:51 +0530438 const std::string& data, bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200439 BMCWEB_LOG_DEBUG << "nbd-proxy.onmessage(len = " << data.length()
440 << ")";
441 // Acquire proxy from sessions
442 auto session = sessions.find(&conn);
443 if (session != sessions.end())
444 {
445 if (session->second)
446 {
447 session->second->send(data);
448 return;
449 }
450 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200451 });
452}
453} // namespace nbd_proxy
454} // namespace crow