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 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 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("/ibm/v1/Host/ConfigFiles/" + 235 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"] = { 247 {"target", 248 "/ibm/v1/Host/ConfigFiles/Actions/IBMConfigFiles.DeleteAll"}}; 249 } 250 251 inline void 252 deleteConfigFiles(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 253 { 254 std::error_code ec; 255 std::filesystem::path loc( 256 "/var/lib/bmcweb/ibm-management-console/configfiles"); 257 if (std::filesystem::exists(loc) && std::filesystem::is_directory(loc)) 258 { 259 std::filesystem::remove_all(loc, ec); 260 if (ec) 261 { 262 asyncResp->res.result( 263 boost::beast::http::status::internal_server_error); 264 asyncResp->res.jsonValue["Description"] = internalServerError; 265 BMCWEB_LOG_DEBUG("deleteConfigFiles: Failed to delete the " 266 "config files directory. ec : {}", 267 ec.message()); 268 } 269 } 270 } 271 272 inline void handleFileGet(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 273 const std::string& fileID) 274 { 275 BMCWEB_LOG_DEBUG("HandleGet on SaveArea files on path: {}", fileID); 276 std::filesystem::path loc( 277 "/var/lib/bmcweb/ibm-management-console/configfiles/" + fileID); 278 if (!std::filesystem::exists(loc) || !std::filesystem::is_regular_file(loc)) 279 { 280 BMCWEB_LOG_WARNING("{} Not found", loc.string()); 281 asyncResp->res.result(boost::beast::http::status::not_found); 282 asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg; 283 return; 284 } 285 286 std::ifstream readfile(loc.string()); 287 if (!readfile) 288 { 289 BMCWEB_LOG_WARNING("{} Not found", loc.string()); 290 asyncResp->res.result(boost::beast::http::status::not_found); 291 asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg; 292 return; 293 } 294 295 std::string contentDispositionParam = "attachment; filename=\"" + fileID + 296 "\""; 297 asyncResp->res.addHeader(boost::beast::http::field::content_disposition, 298 contentDispositionParam); 299 std::string fileData; 300 fileData = {std::istreambuf_iterator<char>(readfile), 301 std::istreambuf_iterator<char>()}; 302 asyncResp->res.jsonValue["Data"] = fileData; 303 } 304 305 inline void 306 handleFileDelete(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 307 const std::string& fileID) 308 { 309 std::string filePath("/var/lib/bmcweb/ibm-management-console/configfiles/" + 310 fileID); 311 BMCWEB_LOG_DEBUG("Removing the file : {}", filePath); 312 std::ifstream fileOpen(filePath.c_str()); 313 if (static_cast<bool>(fileOpen)) 314 { 315 if (remove(filePath.c_str()) == 0) 316 { 317 BMCWEB_LOG_DEBUG("File removed!"); 318 asyncResp->res.jsonValue["Description"] = "File Deleted"; 319 } 320 else 321 { 322 BMCWEB_LOG_ERROR("File not removed!"); 323 asyncResp->res.result( 324 boost::beast::http::status::internal_server_error); 325 asyncResp->res.jsonValue["Description"] = internalServerError; 326 } 327 } 328 else 329 { 330 BMCWEB_LOG_WARNING("File not found!"); 331 asyncResp->res.result(boost::beast::http::status::not_found); 332 asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg; 333 } 334 } 335 336 inline void 337 handleBroadcastService(const crow::Request& req, 338 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 339 { 340 std::string broadcastMsg; 341 342 if (!redfish::json_util::readJsonPatch(req, asyncResp->res, "Message", 343 broadcastMsg)) 344 { 345 BMCWEB_LOG_DEBUG("Not a Valid JSON"); 346 asyncResp->res.result(boost::beast::http::status::bad_request); 347 return; 348 } 349 if (broadcastMsg.size() > maxBroadcastMsgSize) 350 { 351 BMCWEB_LOG_ERROR("Message size exceeds maximum allowed size[1KB]"); 352 asyncResp->res.result(boost::beast::http::status::bad_request); 353 return; 354 } 355 } 356 357 inline void handleFileUrl(const crow::Request& req, 358 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 359 const std::string& fileID) 360 { 361 if (req.method() == boost::beast::http::verb::put) 362 { 363 handleFilePut(req, asyncResp, fileID); 364 return; 365 } 366 if (req.method() == boost::beast::http::verb::get) 367 { 368 handleFileGet(asyncResp, fileID); 369 return; 370 } 371 if (req.method() == boost::beast::http::verb::delete_) 372 { 373 handleFileDelete(asyncResp, fileID); 374 return; 375 } 376 } 377 378 inline bool isValidConfigFileName(const std::string& fileName, 379 crow::Response& res) 380 { 381 if (fileName.empty()) 382 { 383 BMCWEB_LOG_ERROR("Empty filename"); 384 res.jsonValue["Description"] = "Empty file path in the url"; 385 return false; 386 } 387 388 // ConfigFile name is allowed to take upper and lowercase letters, 389 // numbers and hyphen 390 std::size_t found = fileName.find_first_not_of( 391 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"); 392 if (found != std::string::npos) 393 { 394 BMCWEB_LOG_ERROR("Unsupported character in filename: {}", fileName); 395 res.jsonValue["Description"] = "Unsupported character in filename"; 396 return false; 397 } 398 399 // Check the filename length 400 if (fileName.length() > 20) 401 { 402 BMCWEB_LOG_ERROR("Name must be maximum 20 characters. " 403 "Input filename length is: {}", 404 fileName.length()); 405 res.jsonValue["Description"] = "Filename must be maximum 20 characters"; 406 return false; 407 } 408 409 return true; 410 } 411 412 inline void requestRoutes(App& app) 413 { 414 // allowed only for admin 415 BMCWEB_ROUTE(app, "/ibm/v1/") 416 .privileges({{"ConfigureComponents", "ConfigureManager"}}) 417 .methods(boost::beast::http::verb::get)( 418 [](const crow::Request&, 419 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 420 asyncResp->res.jsonValue["@odata.type"] = 421 "#ibmServiceRoot.v1_0_0.ibmServiceRoot"; 422 asyncResp->res.jsonValue["@odata.id"] = "/ibm/v1/"; 423 asyncResp->res.jsonValue["Id"] = "IBM Rest RootService"; 424 asyncResp->res.jsonValue["Name"] = "IBM Service Root"; 425 asyncResp->res.jsonValue["ConfigFiles"]["@odata.id"] = 426 "/ibm/v1/Host/ConfigFiles"; 427 asyncResp->res.jsonValue["BroadcastService"]["@odata.id"] = 428 "/ibm/v1/HMC/BroadcastService"; 429 }); 430 431 BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles") 432 .privileges({{"ConfigureComponents", "ConfigureManager"}}) 433 .methods(boost::beast::http::verb::get)( 434 [](const crow::Request&, 435 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 436 handleConfigFileList(asyncResp); 437 }); 438 439 BMCWEB_ROUTE(app, 440 "/ibm/v1/Host/ConfigFiles/Actions/IBMConfigFiles.DeleteAll") 441 .privileges({{"ConfigureComponents", "ConfigureManager"}}) 442 .methods(boost::beast::http::verb::post)( 443 [](const crow::Request&, 444 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 445 deleteConfigFiles(asyncResp); 446 }); 447 448 BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles/<str>") 449 .privileges({{"ConfigureComponents", "ConfigureManager"}}) 450 .methods(boost::beast::http::verb::put, boost::beast::http::verb::get, 451 boost::beast::http::verb::delete_)( 452 [](const crow::Request& req, 453 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 454 const std::string& fileName) { 455 BMCWEB_LOG_DEBUG("ConfigFile : {}", fileName); 456 // Validate the incoming fileName 457 if (!isValidConfigFileName(fileName, asyncResp->res)) 458 { 459 asyncResp->res.result(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