blob: 212c1db370ffc9ac738222312b7f66bd0d70017b [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
20#include <boost/asio.hpp>
21#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>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020026#include <webserver_common.hpp>
27
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),
48 acceptor(connIn.get_io_context(), stream_protocol::endpoint(socketId)),
49 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,
93 const bool status) {
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
251void requestRoutes(CrowApp& app)
252{
253 BMCWEB_ROUTE(app, "/nbd/<str>")
254 .websocket()
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100255 .onopen([](crow::websocket::Connection& conn,
256 std::shared_ptr<bmcweb::AsyncResp> asyncResp) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200257 BMCWEB_LOG_DEBUG << "nbd-proxy.onopen(" << &conn << ")";
258
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200259 auto getUserInfoHandler =
260 [&conn, asyncResp](
261 const boost::system::error_code ec,
262 boost::container::flat_map<
263 std::string, std::variant<bool, std::string,
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100264 std::vector<std::string>>>
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200265 userInfo) {
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100266 if (ec)
267 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200268 BMCWEB_LOG_ERROR << "GetUserInfo failed...";
269 conn.close("Failed to get user information");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100270 return;
271 }
272
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200273 const std::string* userRolePtr = nullptr;
274 auto userInfoIter = userInfo.find("UserPrivilege");
275 if (userInfoIter != userInfo.end())
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100276 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200277 userRolePtr =
278 std::get_if<std::string>(&userInfoIter->second);
279 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100280
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200281 std::string userRole{};
282 if (userRolePtr != nullptr)
283 {
284 userRole = *userRolePtr;
285 BMCWEB_LOG_DEBUG << "userName = " << conn.getUserName()
286 << " userRole = " << *userRolePtr;
287 }
288
289 // Get the user privileges from the role
290 ::redfish::Privileges userPrivileges =
291 ::redfish::getUserPrivileges(userRole);
292
293 const ::redfish::Privileges requiredPrivileges{
294 requiredPrivilegeString};
295
296 if (!userPrivileges.isSupersetOf(requiredPrivileges))
297 {
298 BMCWEB_LOG_DEBUG
299 << "User " << conn.getUserName()
300 << " not authorized for nbd connection";
301 conn.close("Unathourized access");
302 return;
303 }
304
305 auto openHandler = [&conn, asyncResp](
306 const boost::system::error_code ec,
307 const dbus::utility::
308 ManagedObjectType& objects) {
309 const std::string* socketValue = nullptr;
310 const std::string* endpointValue = nullptr;
311 const std::string* endpointObjectPath = nullptr;
312
313 if (ec)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100314 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200315 BMCWEB_LOG_ERROR << "DBus error: " << ec.message();
316 conn.close("Failed to create mount point");
317 return;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100318 }
319
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200320 for (const auto& objectPath : objects)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100321 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200322 const auto interfaceMap = objectPath.second.find(
323 "xyz.openbmc_project.VirtualMedia.MountPoint");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100324
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200325 if (interfaceMap == objectPath.second.end())
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100326 {
327 BMCWEB_LOG_DEBUG
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200328 << "Cannot find MountPoint object";
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100329 continue;
330 }
331
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200332 const auto endpoint =
333 interfaceMap->second.find("EndpointId");
334 if (endpoint == interfaceMap->second.end())
335 {
336 BMCWEB_LOG_DEBUG
337 << "Cannot find EndpointId property";
338 continue;
339 }
340
341 endpointValue =
342 std::get_if<std::string>(&endpoint->second);
343
344 if (endpointValue == nullptr)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100345 {
346 BMCWEB_LOG_ERROR
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200347 << "EndpointId property value is null";
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100348 continue;
349 }
350
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200351 if (*endpointValue == conn.req.target())
352 {
353 const auto socket =
354 interfaceMap->second.find("Socket");
355 if (socket == interfaceMap->second.end())
356 {
357 BMCWEB_LOG_DEBUG
358 << "Cannot find Socket property";
359 continue;
360 }
361
362 socketValue =
363 std::get_if<std::string>(&socket->second);
364 if (socketValue == nullptr)
365 {
366 BMCWEB_LOG_ERROR
367 << "Socket property value is null";
368 continue;
369 }
370
371 endpointObjectPath = &objectPath.first.str;
372 break;
373 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100374 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100375
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200376 if (objects.empty() || endpointObjectPath == nullptr)
377 {
378 BMCWEB_LOG_ERROR
379 << "Cannot find requested EndpointId";
380 conn.close("Failed to match EndpointId");
381 return;
382 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100383
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200384 for (const auto& session : sessions)
385 {
386 if (session.second->getEndpointId() ==
387 conn.req.target())
388 {
389 BMCWEB_LOG_ERROR
390 << "Cannot open new connection - socket is "
391 "in use";
392 conn.close("Slot is in use");
393 return;
394 }
395 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100396
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200397 // If the socket file exists (i.e. after bmcweb crash),
398 // we cannot reuse it.
399 std::remove((*socketValue).c_str());
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100400
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200401 sessions[&conn] = std::make_shared<NbdProxyServer>(
402 conn, std::move(*socketValue),
403 std::move(*endpointValue),
404 std::move(*endpointObjectPath));
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100405
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200406 sessions[&conn]->run();
407 };
408 crow::connections::systemBus->async_method_call(
409 std::move(openHandler),
410 "xyz.openbmc_project.VirtualMedia",
411 "/xyz/openbmc_project/VirtualMedia",
412 "org.freedesktop.DBus.ObjectManager",
413 "GetManagedObjects");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100414 };
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100415
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200416 crow::connections::systemBus->async_method_call(
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100417 std::move(getUserInfoHandler),
418 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
419 "xyz.openbmc_project.User.Manager", "GetUserInfo",
420 conn.getUserName());
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200421 })
422 .onclose(
423 [](crow::websocket::Connection& conn, const std::string& reason) {
424 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason
425 << "')";
426 auto session = sessions.find(&conn);
427 if (session == sessions.end())
428 {
429 BMCWEB_LOG_DEBUG << "No session to close";
430 return;
431 }
Iwona Winiarska123e8232019-11-29 12:34:33 +0100432 // Remove reference to session in global map
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200433 sessions.erase(session);
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200434 session->second->close();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200435 })
436 .onmessage([](crow::websocket::Connection& conn,
437 const std::string& data, bool isBinary) {
438 BMCWEB_LOG_DEBUG << "nbd-proxy.onmessage(len = " << data.length()
439 << ")";
440 // Acquire proxy from sessions
441 auto session = sessions.find(&conn);
442 if (session != sessions.end())
443 {
444 if (session->second)
445 {
446 session->second->send(data);
447 return;
448 }
449 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200450 });
451}
452} // namespace nbd_proxy
453} // namespace crow