blob: ee0d9cd557cb74de6cacd7627b3bd00e35e619a0 [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
Gunnar Mills1214b7e2020-06-04 10:11:30 -050027#include <variant>
28
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020029namespace crow
30{
31
32namespace nbd_proxy
33{
34
35using boost::asio::local::stream_protocol;
36
37static constexpr auto nbdBufferSize = 131088;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +010038static const char* requiredPrivilegeString = "ConfigureManager";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020039
40struct NbdProxyServer : std::enable_shared_from_this<NbdProxyServer>
41{
42 NbdProxyServer(crow::websocket::Connection& connIn,
43 const std::string& socketIdIn,
44 const std::string& endpointIdIn, const std::string& pathIn) :
45 socketId(socketIdIn),
46 endpointId(endpointIdIn), path(pathIn),
47 acceptor(connIn.get_io_context(), stream_protocol::endpoint(socketId)),
48 connection(connIn)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050049 {}
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020050
51 ~NbdProxyServer()
52 {
53 BMCWEB_LOG_DEBUG << "NbdProxyServer destructor";
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020054 }
55
56 std::string getEndpointId() const
57 {
58 return endpointId;
59 }
60
61 void run()
62 {
63 acceptor.async_accept(
64 [this, self(shared_from_this())](boost::system::error_code ec,
65 stream_protocol::socket socket) {
66 if (ec)
67 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010068 BMCWEB_LOG_ERROR << "UNIX socket: async_accept error = "
69 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020070 return;
71 }
72 if (peerSocket)
73 {
74 // Something is wrong - socket shouldn't be acquired at this
75 // point
76 BMCWEB_LOG_ERROR
77 << "Failed to open connection - socket already used";
78 return;
79 }
80
81 BMCWEB_LOG_DEBUG << "Connection opened";
82 peerSocket = std::move(socket);
83 doRead();
84
85 // Trigger Write if any data was sent from server
86 // Initially this is negotiation chunk
87 doWrite();
88 });
89
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +020090 auto mountHandler = [this, self(shared_from_this())](
91 const boost::system::error_code ec,
Vikram Bodireddyf5b16f02020-08-26 14:54:51 +053092 const bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020093 if (ec)
94 {
Iwona Winiarska123e8232019-11-29 12:34:33 +010095 BMCWEB_LOG_ERROR << "DBus error: cannot call mount method = "
96 << ec.message();
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +020097 connection.close("Failed to mount media");
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020098 return;
99 }
100 };
101
102 crow::connections::systemBus->async_method_call(
103 std::move(mountHandler), "xyz.openbmc_project.VirtualMedia", path,
104 "xyz.openbmc_project.VirtualMedia.Proxy", "Mount");
105 }
106
107 void send(const std::string_view data)
108 {
109 boost::asio::buffer_copy(ws2uxBuf.prepare(data.size()),
110 boost::asio::buffer(data));
111 ws2uxBuf.commit(data.size());
112 doWrite();
113 }
114
115 void close()
116 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100117 acceptor.close();
118 if (peerSocket)
119 {
120 BMCWEB_LOG_DEBUG << "peerSocket->close()";
121 peerSocket->close();
122 peerSocket.reset();
123 BMCWEB_LOG_DEBUG << "std::remove(" << socketId << ")";
124 std::remove(socketId.c_str());
125 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200126 // The reference to session should exists until unmount is
127 // called
128 auto unmountHandler = [](const boost::system::error_code ec) {
129 if (ec)
130 {
131 BMCWEB_LOG_ERROR << "DBus error: " << ec
132 << ", cannot call unmount method";
133 return;
134 }
135 };
136
137 crow::connections::systemBus->async_method_call(
138 std::move(unmountHandler), "xyz.openbmc_project.VirtualMedia", path,
139 "xyz.openbmc_project.VirtualMedia.Proxy", "Unmount");
140 }
141
142 private:
143 void doRead()
144 {
145 if (!peerSocket)
146 {
147 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
148 // Skip if UNIX socket is not created yet.
149 return;
150 }
151
152 // Trigger async read
153 peerSocket->async_read_some(
154 ux2wsBuf.prepare(nbdBufferSize),
155 [this, self(shared_from_this())](boost::system::error_code ec,
156 std::size_t bytesRead) {
157 if (ec)
158 {
159 BMCWEB_LOG_ERROR << "UNIX socket: async_read_some error = "
Iwona Winiarska123e8232019-11-29 12:34:33 +0100160 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200161 // UNIX socket has been closed by peer, best we can do is to
162 // break all connections
163 close();
164 return;
165 }
166
167 // Fetch data from UNIX socket
168
169 ux2wsBuf.commit(bytesRead);
170
171 // Paste it to WebSocket as binary
172 connection.sendBinary(
173 boost::beast::buffers_to_string(ux2wsBuf.data()));
174 ux2wsBuf.consume(bytesRead);
175
176 // Allow further reads
177 doRead();
178 });
179 }
180
181 void doWrite()
182 {
183 if (!peerSocket)
184 {
185 BMCWEB_LOG_DEBUG << "UNIX socket isn't created yet";
186 // Skip if UNIX socket is not created yet. Collect data, and wait
187 // for nbd-client connection
188 return;
189 }
190
191 if (uxWriteInProgress)
192 {
193 BMCWEB_LOG_ERROR << "Write in progress";
194 return;
195 }
196
197 if (ws2uxBuf.size() == 0)
198 {
199 BMCWEB_LOG_ERROR << "No data to write to UNIX socket";
200 return;
201 }
202
203 uxWriteInProgress = true;
204 boost::asio::async_write(
205 *peerSocket, ws2uxBuf.data(),
206 [this, self(shared_from_this())](boost::system::error_code ec,
207 std::size_t bytesWritten) {
208 ws2uxBuf.consume(bytesWritten);
209 uxWriteInProgress = false;
210 if (ec)
211 {
Iwona Winiarska123e8232019-11-29 12:34:33 +0100212 BMCWEB_LOG_ERROR << "UNIX: async_write error = "
213 << ec.message();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200214 return;
215 }
216 // Retrigger doWrite if there is something in buffer
Iwona Winiarska123e8232019-11-29 12:34:33 +0100217 if (ws2uxBuf.size() > 0)
218 {
219 doWrite();
220 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200221 });
222 }
223
224 // Keeps UNIX socket endpoint file path
225 const std::string socketId;
226 const std::string endpointId;
227 const std::string path;
228
229 bool uxWriteInProgress = false;
230
231 // UNIX => WebSocket buffer
232 boost::beast::multi_buffer ux2wsBuf;
233
234 // WebSocket <= UNIX buffer
235 boost::beast::multi_buffer ws2uxBuf;
236
237 // Default acceptor for UNIX socket
238 stream_protocol::acceptor acceptor;
239
240 // The socket used to communicate with the client.
241 std::optional<stream_protocol::socket> peerSocket;
242
243 crow::websocket::Connection& connection;
244};
245
246static boost::container::flat_map<crow::websocket::Connection*,
247 std::shared_ptr<NbdProxyServer>>
248 sessions;
249
Ed Tanous52cc1122020-07-18 13:51:21 -0700250void requestRoutes(App& app)
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200251{
252 BMCWEB_ROUTE(app, "/nbd/<str>")
253 .websocket()
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100254 .onopen([](crow::websocket::Connection& conn,
255 std::shared_ptr<bmcweb::AsyncResp> asyncResp) {
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 =
259 [&conn, asyncResp](
260 const boost::system::error_code ec,
261 boost::container::flat_map<
262 std::string, std::variant<bool, std::string,
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100263 std::vector<std::string>>>
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200264 userInfo) {
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100265 if (ec)
266 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200267 BMCWEB_LOG_ERROR << "GetUserInfo failed...";
268 conn.close("Failed to get user information");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100269 return;
270 }
271
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200272 const std::string* userRolePtr = nullptr;
273 auto userInfoIter = userInfo.find("UserPrivilege");
274 if (userInfoIter != userInfo.end())
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100275 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200276 userRolePtr =
277 std::get_if<std::string>(&userInfoIter->second);
278 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100279
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200280 std::string userRole{};
281 if (userRolePtr != nullptr)
282 {
283 userRole = *userRolePtr;
284 BMCWEB_LOG_DEBUG << "userName = " << conn.getUserName()
285 << " userRole = " << *userRolePtr;
286 }
287
288 // Get the user privileges from the role
289 ::redfish::Privileges userPrivileges =
290 ::redfish::getUserPrivileges(userRole);
291
292 const ::redfish::Privileges requiredPrivileges{
293 requiredPrivilegeString};
294
295 if (!userPrivileges.isSupersetOf(requiredPrivileges))
296 {
297 BMCWEB_LOG_DEBUG
298 << "User " << conn.getUserName()
299 << " not authorized for nbd connection";
300 conn.close("Unathourized access");
301 return;
302 }
303
304 auto openHandler = [&conn, asyncResp](
305 const boost::system::error_code ec,
306 const dbus::utility::
307 ManagedObjectType& objects) {
308 const std::string* socketValue = nullptr;
309 const std::string* endpointValue = nullptr;
310 const std::string* endpointObjectPath = nullptr;
311
312 if (ec)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100313 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200314 BMCWEB_LOG_ERROR << "DBus error: " << ec.message();
315 conn.close("Failed to create mount point");
316 return;
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100317 }
318
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200319 for (const auto& objectPath : objects)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100320 {
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200321 const auto interfaceMap = objectPath.second.find(
322 "xyz.openbmc_project.VirtualMedia.MountPoint");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100323
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200324 if (interfaceMap == objectPath.second.end())
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100325 {
326 BMCWEB_LOG_DEBUG
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200327 << "Cannot find MountPoint object";
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100328 continue;
329 }
330
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200331 const auto endpoint =
332 interfaceMap->second.find("EndpointId");
333 if (endpoint == interfaceMap->second.end())
334 {
335 BMCWEB_LOG_DEBUG
336 << "Cannot find EndpointId property";
337 continue;
338 }
339
340 endpointValue =
341 std::get_if<std::string>(&endpoint->second);
342
343 if (endpointValue == nullptr)
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100344 {
345 BMCWEB_LOG_ERROR
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200346 << "EndpointId property value is null";
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100347 continue;
348 }
349
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200350 if (*endpointValue == conn.req.target())
351 {
352 const auto socket =
353 interfaceMap->second.find("Socket");
354 if (socket == interfaceMap->second.end())
355 {
356 BMCWEB_LOG_DEBUG
357 << "Cannot find Socket property";
358 continue;
359 }
360
361 socketValue =
362 std::get_if<std::string>(&socket->second);
363 if (socketValue == nullptr)
364 {
365 BMCWEB_LOG_ERROR
366 << "Socket property value is null";
367 continue;
368 }
369
370 endpointObjectPath = &objectPath.first.str;
371 break;
372 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100373 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100374
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200375 if (objects.empty() || endpointObjectPath == nullptr)
376 {
377 BMCWEB_LOG_ERROR
378 << "Cannot find requested EndpointId";
379 conn.close("Failed to match EndpointId");
380 return;
381 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100382
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200383 for (const auto& session : sessions)
384 {
385 if (session.second->getEndpointId() ==
386 conn.req.target())
387 {
388 BMCWEB_LOG_ERROR
389 << "Cannot open new connection - socket is "
390 "in use";
391 conn.close("Slot is in use");
392 return;
393 }
394 }
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100395
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200396 // If the socket file exists (i.e. after bmcweb crash),
397 // we cannot reuse it.
398 std::remove((*socketValue).c_str());
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100399
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200400 sessions[&conn] = std::make_shared<NbdProxyServer>(
401 conn, std::move(*socketValue),
402 std::move(*endpointValue),
403 std::move(*endpointObjectPath));
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100404
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200405 sessions[&conn]->run();
406 };
407 crow::connections::systemBus->async_method_call(
408 std::move(openHandler),
409 "xyz.openbmc_project.VirtualMedia",
410 "/xyz/openbmc_project/VirtualMedia",
411 "org.freedesktop.DBus.ObjectManager",
412 "GetManagedObjects");
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100413 };
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100414
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200415 crow::connections::systemBus->async_method_call(
Przemyslaw Czarnowski250b0eb2020-02-24 10:23:56 +0100416 std::move(getUserInfoHandler),
417 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
418 "xyz.openbmc_project.User.Manager", "GetUserInfo",
419 conn.getUserName());
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200420 })
421 .onclose(
422 [](crow::websocket::Connection& conn, const std::string& reason) {
423 BMCWEB_LOG_DEBUG << "nbd-proxy.onclose(reason = '" << reason
424 << "')";
425 auto session = sessions.find(&conn);
426 if (session == sessions.end())
427 {
428 BMCWEB_LOG_DEBUG << "No session to close";
429 return;
430 }
Iwona Winiarska123e8232019-11-29 12:34:33 +0100431 // Remove reference to session in global map
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200432 sessions.erase(session);
Wludzik, Jozeff6a0d632020-07-16 15:16:02 +0200433 session->second->close();
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200434 })
435 .onmessage([](crow::websocket::Connection& conn,
Vikram Bodireddyf5b16f02020-08-26 14:54:51 +0530436 const std::string& data, bool) {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200437 BMCWEB_LOG_DEBUG << "nbd-proxy.onmessage(len = " << data.length()
438 << ")";
439 // Acquire proxy from sessions
440 auto session = sessions.find(&conn);
441 if (session != sessions.end())
442 {
443 if (session->second)
444 {
445 session->second->send(data);
446 return;
447 }
448 }
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200449 });
450}
451} // namespace nbd_proxy
452} // namespace crow