1 #pragma once
2
3 #include "app.hpp"
4 #include "async_resp.hpp"
5 #include "error_messages.hpp"
6 #include "event_service_manager.hpp"
7 #include "ibm/utils.hpp"
8 #include "resource_messages.hpp"
9 #include "str_utility.hpp"
10 #include "utils/json_utils.hpp"
11
12 #include <boost/container/flat_set.hpp>
13 #include <nlohmann/json.hpp>
14 #include <sdbusplus/message/types.hpp>
15
16 #include <filesystem>
17 #include <fstream>
18
19 namespace crow
20 {
21 namespace ibm_mc
22 {
23 constexpr const char* methodNotAllowedMsg = "Method Not Allowed";
24 constexpr const char* resourceNotFoundMsg = "Resource Not Found";
25 constexpr const char* contentNotAcceptableMsg = "Content Not Acceptable";
26 constexpr const char* internalServerError = "Internal Server Error";
27
28 constexpr size_t maxSaveareaDirSize =
29 25000000; // Allow save area dir size to be max 25MB
30 constexpr size_t minSaveareaFileSize =
31 100; // Allow save area file size of minimum 100B
32 constexpr size_t maxSaveareaFileSize =
33 500000; // Allow save area file size upto 500KB
34 constexpr size_t maxBroadcastMsgSize =
35 1000; // Allow Broadcast message size upto 1KB
36
handleFilePut(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fileID)37 inline void handleFilePut(const crow::Request& req,
38 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
39 const std::string& fileID)
40 {
41 std::error_code ec;
42 // Check the content-type of the request
43 boost::beast::string_view contentType = req.getHeaderValue("content-type");
44 if (!bmcweb::asciiIEquals(contentType, "application/octet-stream"))
45 {
46 asyncResp->res.result(boost::beast::http::status::not_acceptable);
47 asyncResp->res.jsonValue["Description"] = contentNotAcceptableMsg;
48 return;
49 }
50 BMCWEB_LOG_DEBUG(
51 "File upload in application/octet-stream format. Continue..");
52
53 BMCWEB_LOG_DEBUG(
54 "handleIbmPut: Request to create/update the save-area file");
55 std::string_view path =
56 "/var/lib/bmcweb/ibm-management-console/configfiles";
57 if (!crow::ibm_utils::createDirectory(path))
58 {
59 asyncResp->res.result(boost::beast::http::status::not_found);
60 asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg;
61 return;
62 }
63
64 std::ofstream file;
65 std::filesystem::path loc(
66 "/var/lib/bmcweb/ibm-management-console/configfiles");
67
68 // Get the current size of the savearea directory
69 std::filesystem::recursive_directory_iterator iter(loc, ec);
70 if (ec)
71 {
72 asyncResp->res.result(
73 boost::beast::http::status::internal_server_error);
74 asyncResp->res.jsonValue["Description"] = internalServerError;
75 BMCWEB_LOG_DEBUG("handleIbmPut: Failed to prepare save-area "
76 "directory iterator. ec : {}",
77 ec.message());
78 return;
79 }
80 std::uintmax_t saveAreaDirSize = 0;
81 for (const auto& it : iter)
82 {
83 if (!std::filesystem::is_directory(it, ec))
84 {
85 if (ec)
86 {
87 asyncResp->res.result(
88 boost::beast::http::status::internal_server_error);
89 asyncResp->res.jsonValue["Description"] = internalServerError;
90 BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find save-area "
91 "directory . ec : {}",
92 ec.message());
93 return;
94 }
95 std::uintmax_t fileSize = std::filesystem::file_size(it, ec);
96 if (ec)
97 {
98 asyncResp->res.result(
99 boost::beast::http::status::internal_server_error);
100 asyncResp->res.jsonValue["Description"] = internalServerError;
101 BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find save-area "
102 "file size inside the directory . ec : {}",
103 ec.message());
104 return;
105 }
106 saveAreaDirSize += fileSize;
107 }
108 }
109 BMCWEB_LOG_DEBUG("saveAreaDirSize: {}", saveAreaDirSize);
110
111 // Get the file size getting uploaded
112 const std::string& data = req.body();
113 BMCWEB_LOG_DEBUG("data length: {}", data.length());
114
115 if (data.length() < minSaveareaFileSize)
116 {
117 asyncResp->res.result(boost::beast::http::status::bad_request);
118 asyncResp->res.jsonValue["Description"] =
119 "File size is less than minimum allowed size[100B]";
120 return;
121 }
122 if (data.length() > maxSaveareaFileSize)
123 {
124 asyncResp->res.result(boost::beast::http::status::bad_request);
125 asyncResp->res.jsonValue["Description"] =
126 "File size exceeds maximum allowed size[500KB]";
127 return;
128 }
129
130 // Form the file path
131 loc /= fileID;
132 BMCWEB_LOG_DEBUG("Writing to the file: {}", loc.string());
133
134 // Check if the same file exists in the directory
135 bool fileExists = std::filesystem::exists(loc, ec);
136 if (ec)
137 {
138 asyncResp->res.result(
139 boost::beast::http::status::internal_server_error);
140 asyncResp->res.jsonValue["Description"] = internalServerError;
141 BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find if file exists. ec : {}",
142 ec.message());
143 return;
144 }
145
146 std::uintmax_t newSizeToWrite = 0;
147 if (fileExists)
148 {
149 // File exists. Get the current file size
150 std::uintmax_t currentFileSize = std::filesystem::file_size(loc, ec);
151 if (ec)
152 {
153 asyncResp->res.result(
154 boost::beast::http::status::internal_server_error);
155 asyncResp->res.jsonValue["Description"] = internalServerError;
156 BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find file size. ec : {}",
157 ec.message());
158 return;
159 }
160 // Calculate the difference in the file size.
161 // If the data.length is greater than the existing file size, then
162 // calculate the difference. Else consider the delta size as zero -
163 // because there is no increase in the total directory size.
164 // We need to add the diff only if the incoming data is larger than the
165 // existing filesize
166 if (data.length() > currentFileSize)
167 {
168 newSizeToWrite = data.length() - currentFileSize;
169 }
170 BMCWEB_LOG_DEBUG("newSizeToWrite: {}", newSizeToWrite);
171 }
172 else
173 {
174 // This is a new file upload
175 newSizeToWrite = data.length();
176 }
177
178 // Calculate the total dir size before writing the new file
179 BMCWEB_LOG_DEBUG("total new size: {}", saveAreaDirSize + newSizeToWrite);
180
181 if ((saveAreaDirSize + newSizeToWrite) > maxSaveareaDirSize)
182 {
183 asyncResp->res.result(boost::beast::http::status::bad_request);
184 asyncResp->res.jsonValue["Description"] =
185 "File size does not fit in the savearea "
186 "directory maximum allowed size[25MB]";
187 return;
188 }
189
190 file.open(loc, std::ofstream::out);
191
192 // set the permission of the file to 600
193 std::filesystem::perms permission = std::filesystem::perms::owner_write |
194 std::filesystem::perms::owner_read;
195 std::filesystem::permissions(loc, permission);
196
197 if (file.fail())
198 {
199 BMCWEB_LOG_DEBUG("Error while opening the file for writing");
200 asyncResp->res.result(
201 boost::beast::http::status::internal_server_error);
202 asyncResp->res.jsonValue["Description"] =
203 "Error while creating the file";
204 return;
205 }
206 file << data;
207
208 // Push an event
209 if (fileExists)
210 {
211 BMCWEB_LOG_DEBUG("config file is updated");
212 asyncResp->res.jsonValue["Description"] = "File Updated";
213 }
214 else
215 {
216 BMCWEB_LOG_DEBUG("config file is created");
217 asyncResp->res.jsonValue["Description"] = "File Created";
218 }
219 }
220
221 inline void
handleConfigFileList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)222 handleConfigFileList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
223 {
224 std::vector<std::string> pathObjList;
225 std::filesystem::path loc(
226 "/var/lib/bmcweb/ibm-management-console/configfiles");
227 if (std::filesystem::exists(loc) && std::filesystem::is_directory(loc))
228 {
229 for (const auto& file : std::filesystem::directory_iterator(loc))
230 {
231 const std::filesystem::path& pathObj = file.path();
232 if (std::filesystem::is_regular_file(pathObj))
233 {
234 pathObjList.emplace_back(
235 "/ibm/v1/Host/ConfigFiles/" + pathObj.filename().string());
236 }
237 }
238 }
239 asyncResp->res.jsonValue["@odata.type"] =
240 "#IBMConfigFile.v1_0_0.IBMConfigFile";
241 asyncResp->res.jsonValue["@odata.id"] = "/ibm/v1/Host/ConfigFiles/";
242 asyncResp->res.jsonValue["Id"] = "ConfigFiles";
243 asyncResp->res.jsonValue["Name"] = "ConfigFiles";
244
245 asyncResp->res.jsonValue["Members"] = std::move(pathObjList);
246 asyncResp->res.jsonValue["Actions"]["#IBMConfigFiles.DeleteAll"]["target"] =
247 "/ibm/v1/Host/ConfigFiles/Actions/IBMConfigFiles.DeleteAll";
248 }
249
250 inline void
deleteConfigFiles(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)251 deleteConfigFiles(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
252 {
253 std::error_code ec;
254 std::filesystem::path loc(
255 "/var/lib/bmcweb/ibm-management-console/configfiles");
256 if (std::filesystem::exists(loc) && std::filesystem::is_directory(loc))
257 {
258 std::filesystem::remove_all(loc, ec);
259 if (ec)
260 {
261 asyncResp->res.result(
262 boost::beast::http::status::internal_server_error);
263 asyncResp->res.jsonValue["Description"] = internalServerError;
264 BMCWEB_LOG_DEBUG("deleteConfigFiles: Failed to delete the "
265 "config files directory. ec : {}",
266 ec.message());
267 }
268 }
269 }
270
handleFileGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fileID)271 inline void handleFileGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
272 const std::string& fileID)
273 {
274 BMCWEB_LOG_DEBUG("HandleGet on SaveArea files on path: {}", fileID);
275 std::filesystem::path loc(
276 "/var/lib/bmcweb/ibm-management-console/configfiles/" + fileID);
277 if (!std::filesystem::exists(loc) || !std::filesystem::is_regular_file(loc))
278 {
279 BMCWEB_LOG_WARNING("{} Not found", loc.string());
280 asyncResp->res.result(boost::beast::http::status::not_found);
281 asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg;
282 return;
283 }
284
285 std::ifstream readfile(loc.string());
286 if (!readfile)
287 {
288 BMCWEB_LOG_WARNING("{} Not found", loc.string());
289 asyncResp->res.result(boost::beast::http::status::not_found);
290 asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg;
291 return;
292 }
293
294 std::string contentDispositionParam =
295 "attachment; filename=\"" + fileID + "\"";
296 asyncResp->res.addHeader(boost::beast::http::field::content_disposition,
297 contentDispositionParam);
298 std::string fileData;
299 fileData = {std::istreambuf_iterator<char>(readfile),
300 std::istreambuf_iterator<char>()};
301 asyncResp->res.jsonValue["Data"] = fileData;
302 }
303
304 inline void
handleFileDelete(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fileID)305 handleFileDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
306 const std::string& fileID)
307 {
308 std::string filePath(
309 "/var/lib/bmcweb/ibm-management-console/configfiles/" + fileID);
310 BMCWEB_LOG_DEBUG("Removing the file : {}", filePath);
311 std::ifstream fileOpen(filePath.c_str());
312 if (static_cast<bool>(fileOpen))
313 {
314 if (remove(filePath.c_str()) == 0)
315 {
316 BMCWEB_LOG_DEBUG("File removed!");
317 asyncResp->res.jsonValue["Description"] = "File Deleted";
318 }
319 else
320 {
321 BMCWEB_LOG_ERROR("File not removed!");
322 asyncResp->res.result(
323 boost::beast::http::status::internal_server_error);
324 asyncResp->res.jsonValue["Description"] = internalServerError;
325 }
326 }
327 else
328 {
329 BMCWEB_LOG_WARNING("File not found!");
330 asyncResp->res.result(boost::beast::http::status::not_found);
331 asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg;
332 }
333 }
334
335 inline void
handleBroadcastService(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)336 handleBroadcastService(const crow::Request& req,
337 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
338 {
339 std::string broadcastMsg;
340
341 if (!redfish::json_util::readJsonPatch(req, asyncResp->res, "Message",
342 broadcastMsg))
343 {
344 BMCWEB_LOG_DEBUG("Not a Valid JSON");
345 asyncResp->res.result(boost::beast::http::status::bad_request);
346 return;
347 }
348 if (broadcastMsg.size() > maxBroadcastMsgSize)
349 {
350 BMCWEB_LOG_ERROR("Message size exceeds maximum allowed size[1KB]");
351 asyncResp->res.result(boost::beast::http::status::bad_request);
352 return;
353 }
354 }
355
handleFileUrl(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & fileID)356 inline void handleFileUrl(const crow::Request& req,
357 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
358 const std::string& fileID)
359 {
360 if (req.method() == boost::beast::http::verb::put)
361 {
362 handleFilePut(req, asyncResp, fileID);
363 return;
364 }
365 if (req.method() == boost::beast::http::verb::get)
366 {
367 handleFileGet(asyncResp, fileID);
368 return;
369 }
370 if (req.method() == boost::beast::http::verb::delete_)
371 {
372 handleFileDelete(asyncResp, fileID);
373 return;
374 }
375 }
376
isValidConfigFileName(const std::string & fileName,crow::Response & res)377 inline bool isValidConfigFileName(const std::string& fileName,
378 crow::Response& res)
379 {
380 if (fileName.empty())
381 {
382 BMCWEB_LOG_ERROR("Empty filename");
383 res.jsonValue["Description"] = "Empty file path in the url";
384 return false;
385 }
386
387 // ConfigFile name is allowed to take upper and lowercase letters,
388 // numbers and hyphen
389 std::size_t found = fileName.find_first_not_of(
390 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-");
391 if (found != std::string::npos)
392 {
393 BMCWEB_LOG_ERROR("Unsupported character in filename: {}", fileName);
394 res.jsonValue["Description"] = "Unsupported character in filename";
395 return false;
396 }
397
398 // Check the filename length
399 if (fileName.length() > 20)
400 {
401 BMCWEB_LOG_ERROR("Name must be maximum 20 characters. "
402 "Input filename length is: {}",
403 fileName.length());
404 res.jsonValue["Description"] = "Filename must be maximum 20 characters";
405 return false;
406 }
407
408 return true;
409 }
410
requestRoutes(App & app)411 inline void requestRoutes(App& app)
412 {
413 // allowed only for admin
414 BMCWEB_ROUTE(app, "/ibm/v1/")
415 .privileges({{"ConfigureComponents", "ConfigureManager"}})
416 .methods(boost::beast::http::verb::get)(
417 [](const crow::Request&,
418 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
419 asyncResp->res.jsonValue["@odata.type"] =
420 "#ibmServiceRoot.v1_0_0.ibmServiceRoot";
421 asyncResp->res.jsonValue["@odata.id"] = "/ibm/v1/";
422 asyncResp->res.jsonValue["Id"] = "IBM Rest RootService";
423 asyncResp->res.jsonValue["Name"] = "IBM Service Root";
424 asyncResp->res.jsonValue["ConfigFiles"]["@odata.id"] =
425 "/ibm/v1/Host/ConfigFiles";
426 asyncResp->res.jsonValue["BroadcastService"]["@odata.id"] =
427 "/ibm/v1/HMC/BroadcastService";
428 });
429
430 BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles")
431 .privileges({{"ConfigureComponents", "ConfigureManager"}})
432 .methods(boost::beast::http::verb::get)(
433 [](const crow::Request&,
434 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
435 handleConfigFileList(asyncResp);
436 });
437
438 BMCWEB_ROUTE(app,
439 "/ibm/v1/Host/ConfigFiles/Actions/IBMConfigFiles.DeleteAll")
440 .privileges({{"ConfigureComponents", "ConfigureManager"}})
441 .methods(boost::beast::http::verb::post)(
442 [](const crow::Request&,
443 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
444 deleteConfigFiles(asyncResp);
445 });
446
447 BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles/<str>")
448 .privileges({{"ConfigureComponents", "ConfigureManager"}})
449 .methods(boost::beast::http::verb::put, boost::beast::http::verb::get,
450 boost::beast::http::verb::delete_)(
451 [](const crow::Request& req,
452 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
453 const std::string& fileName) {
454 BMCWEB_LOG_DEBUG("ConfigFile : {}", fileName);
455 // Validate the incoming fileName
456 if (!isValidConfigFileName(fileName, asyncResp->res))
457 {
458 asyncResp->res.result(
459 boost::beast::http::status::bad_request);
460 return;
461 }
462 handleFileUrl(req, asyncResp, fileName);
463 });
464
465 BMCWEB_ROUTE(app, "/ibm/v1/HMC/BroadcastService")
466 .privileges({{"ConfigureComponents", "ConfigureManager"}})
467 .methods(boost::beast::http::verb::post)(
468 [](const crow::Request& req,
469 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
470 handleBroadcastService(req, asyncResp);
471 });
472 }
473
474 } // namespace ibm_mc
475 } // namespace crow
476