#pragma once #include "app.hpp" #include "async_resp.hpp" #include "error_messages.hpp" #include "event_service_manager.hpp" #include "ibm/utils.hpp" #include "resource_messages.hpp" #include "str_utility.hpp" #include "utils/json_utils.hpp" #include #include #include #include #include namespace crow { namespace ibm_mc { constexpr const char* methodNotAllowedMsg = "Method Not Allowed"; constexpr const char* resourceNotFoundMsg = "Resource Not Found"; constexpr const char* contentNotAcceptableMsg = "Content Not Acceptable"; constexpr const char* internalServerError = "Internal Server Error"; constexpr size_t maxSaveareaDirSize = 25000000; // Allow save area dir size to be max 25MB constexpr size_t minSaveareaFileSize = 100; // Allow save area file size of minimum 100B constexpr size_t maxSaveareaFileSize = 500000; // Allow save area file size upto 500KB constexpr size_t maxBroadcastMsgSize = 1000; // Allow Broadcast message size upto 1KB inline void handleFilePut(const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& fileID) { std::error_code ec; // Check the content-type of the request boost::beast::string_view contentType = req.getHeaderValue("content-type"); if (!bmcweb::asciiIEquals(contentType, "application/octet-stream")) { asyncResp->res.result(boost::beast::http::status::not_acceptable); asyncResp->res.jsonValue["Description"] = contentNotAcceptableMsg; return; } BMCWEB_LOG_DEBUG( "File upload in application/octet-stream format. Continue.."); BMCWEB_LOG_DEBUG( "handleIbmPut: Request to create/update the save-area file"); std::string_view path = "/var/lib/bmcweb/ibm-management-console/configfiles"; if (!crow::ibm_utils::createDirectory(path)) { asyncResp->res.result(boost::beast::http::status::not_found); asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg; return; } std::ofstream file; std::filesystem::path loc( "/var/lib/bmcweb/ibm-management-console/configfiles"); // Get the current size of the savearea directory std::filesystem::recursive_directory_iterator iter(loc, ec); if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("handleIbmPut: Failed to prepare save-area " "directory iterator. ec : {}", ec.message()); return; } std::uintmax_t saveAreaDirSize = 0; for (const auto& it : iter) { if (!std::filesystem::is_directory(it, ec)) { if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find save-area " "directory . ec : {}", ec.message()); return; } std::uintmax_t fileSize = std::filesystem::file_size(it, ec); if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find save-area " "file size inside the directory . ec : {}", ec.message()); return; } saveAreaDirSize += fileSize; } } BMCWEB_LOG_DEBUG("saveAreaDirSize: {}", saveAreaDirSize); // Get the file size getting uploaded const std::string& data = req.body(); BMCWEB_LOG_DEBUG("data length: {}", data.length()); if (data.length() < minSaveareaFileSize) { asyncResp->res.result(boost::beast::http::status::bad_request); asyncResp->res.jsonValue["Description"] = "File size is less than minimum allowed size[100B]"; return; } if (data.length() > maxSaveareaFileSize) { asyncResp->res.result(boost::beast::http::status::bad_request); asyncResp->res.jsonValue["Description"] = "File size exceeds maximum allowed size[500KB]"; return; } // Form the file path loc /= fileID; BMCWEB_LOG_DEBUG("Writing to the file: {}", loc.string()); // Check if the same file exists in the directory bool fileExists = std::filesystem::exists(loc, ec); if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find if file exists. ec : {}", ec.message()); return; } std::uintmax_t newSizeToWrite = 0; if (fileExists) { // File exists. Get the current file size std::uintmax_t currentFileSize = std::filesystem::file_size(loc, ec); if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("handleIbmPut: Failed to find file size. ec : {}", ec.message()); return; } // Calculate the difference in the file size. // If the data.length is greater than the existing file size, then // calculate the difference. Else consider the delta size as zero - // because there is no increase in the total directory size. // We need to add the diff only if the incoming data is larger than the // existing filesize if (data.length() > currentFileSize) { newSizeToWrite = data.length() - currentFileSize; } BMCWEB_LOG_DEBUG("newSizeToWrite: {}", newSizeToWrite); } else { // This is a new file upload newSizeToWrite = data.length(); } // Calculate the total dir size before writing the new file BMCWEB_LOG_DEBUG("total new size: {}", saveAreaDirSize + newSizeToWrite); if ((saveAreaDirSize + newSizeToWrite) > maxSaveareaDirSize) { asyncResp->res.result(boost::beast::http::status::bad_request); asyncResp->res.jsonValue["Description"] = "File size does not fit in the savearea " "directory maximum allowed size[25MB]"; return; } file.open(loc, std::ofstream::out); // set the permission of the file to 600 std::filesystem::perms permission = std::filesystem::perms::owner_write | std::filesystem::perms::owner_read; std::filesystem::permissions(loc, permission); if (file.fail()) { BMCWEB_LOG_DEBUG("Error while opening the file for writing"); asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = "Error while creating the file"; return; } file << data; // Push an event if (fileExists) { BMCWEB_LOG_DEBUG("config file is updated"); asyncResp->res.jsonValue["Description"] = "File Updated"; } else { BMCWEB_LOG_DEBUG("config file is created"); asyncResp->res.jsonValue["Description"] = "File Created"; } } inline void handleConfigFileList(const std::shared_ptr& asyncResp) { std::vector pathObjList; std::filesystem::path loc( "/var/lib/bmcweb/ibm-management-console/configfiles"); if (std::filesystem::exists(loc) && std::filesystem::is_directory(loc)) { for (const auto& file : std::filesystem::directory_iterator(loc)) { const std::filesystem::path& pathObj = file.path(); if (std::filesystem::is_regular_file(pathObj)) { pathObjList.emplace_back("/ibm/v1/Host/ConfigFiles/" + pathObj.filename().string()); } } } asyncResp->res.jsonValue["@odata.type"] = "#IBMConfigFile.v1_0_0.IBMConfigFile"; asyncResp->res.jsonValue["@odata.id"] = "/ibm/v1/Host/ConfigFiles/"; asyncResp->res.jsonValue["Id"] = "ConfigFiles"; asyncResp->res.jsonValue["Name"] = "ConfigFiles"; asyncResp->res.jsonValue["Members"] = std::move(pathObjList); asyncResp->res.jsonValue["Actions"]["#IBMConfigFiles.DeleteAll"]["target"] = "/ibm/v1/Host/ConfigFiles/Actions/IBMConfigFiles.DeleteAll"; } inline void deleteConfigFiles(const std::shared_ptr& asyncResp) { std::error_code ec; std::filesystem::path loc( "/var/lib/bmcweb/ibm-management-console/configfiles"); if (std::filesystem::exists(loc) && std::filesystem::is_directory(loc)) { std::filesystem::remove_all(loc, ec); if (ec) { asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; BMCWEB_LOG_DEBUG("deleteConfigFiles: Failed to delete the " "config files directory. ec : {}", ec.message()); } } } inline void handleFileGet(const std::shared_ptr& asyncResp, const std::string& fileID) { BMCWEB_LOG_DEBUG("HandleGet on SaveArea files on path: {}", fileID); std::filesystem::path loc( "/var/lib/bmcweb/ibm-management-console/configfiles/" + fileID); if (!std::filesystem::exists(loc) || !std::filesystem::is_regular_file(loc)) { BMCWEB_LOG_WARNING("{} Not found", loc.string()); asyncResp->res.result(boost::beast::http::status::not_found); asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg; return; } std::ifstream readfile(loc.string()); if (!readfile) { BMCWEB_LOG_WARNING("{} Not found", loc.string()); asyncResp->res.result(boost::beast::http::status::not_found); asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg; return; } std::string contentDispositionParam = "attachment; filename=\"" + fileID + "\""; asyncResp->res.addHeader(boost::beast::http::field::content_disposition, contentDispositionParam); std::string fileData; fileData = {std::istreambuf_iterator(readfile), std::istreambuf_iterator()}; asyncResp->res.jsonValue["Data"] = fileData; } inline void handleFileDelete(const std::shared_ptr& asyncResp, const std::string& fileID) { std::string filePath("/var/lib/bmcweb/ibm-management-console/configfiles/" + fileID); BMCWEB_LOG_DEBUG("Removing the file : {}", filePath); std::ifstream fileOpen(filePath.c_str()); if (static_cast(fileOpen)) { if (remove(filePath.c_str()) == 0) { BMCWEB_LOG_DEBUG("File removed!"); asyncResp->res.jsonValue["Description"] = "File Deleted"; } else { BMCWEB_LOG_ERROR("File not removed!"); asyncResp->res.result( boost::beast::http::status::internal_server_error); asyncResp->res.jsonValue["Description"] = internalServerError; } } else { BMCWEB_LOG_WARNING("File not found!"); asyncResp->res.result(boost::beast::http::status::not_found); asyncResp->res.jsonValue["Description"] = resourceNotFoundMsg; } } inline void handleBroadcastService(const crow::Request& req, const std::shared_ptr& asyncResp) { std::string broadcastMsg; if (!redfish::json_util::readJsonPatch(req, asyncResp->res, "Message", broadcastMsg)) { BMCWEB_LOG_DEBUG("Not a Valid JSON"); asyncResp->res.result(boost::beast::http::status::bad_request); return; } if (broadcastMsg.size() > maxBroadcastMsgSize) { BMCWEB_LOG_ERROR("Message size exceeds maximum allowed size[1KB]"); asyncResp->res.result(boost::beast::http::status::bad_request); return; } } inline void handleFileUrl(const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& fileID) { if (req.method() == boost::beast::http::verb::put) { handleFilePut(req, asyncResp, fileID); return; } if (req.method() == boost::beast::http::verb::get) { handleFileGet(asyncResp, fileID); return; } if (req.method() == boost::beast::http::verb::delete_) { handleFileDelete(asyncResp, fileID); return; } } inline bool isValidConfigFileName(const std::string& fileName, crow::Response& res) { if (fileName.empty()) { BMCWEB_LOG_ERROR("Empty filename"); res.jsonValue["Description"] = "Empty file path in the url"; return false; } // ConfigFile name is allowed to take upper and lowercase letters, // numbers and hyphen std::size_t found = fileName.find_first_not_of( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"); if (found != std::string::npos) { BMCWEB_LOG_ERROR("Unsupported character in filename: {}", fileName); res.jsonValue["Description"] = "Unsupported character in filename"; return false; } // Check the filename length if (fileName.length() > 20) { BMCWEB_LOG_ERROR("Name must be maximum 20 characters. " "Input filename length is: {}", fileName.length()); res.jsonValue["Description"] = "Filename must be maximum 20 characters"; return false; } return true; } inline void requestRoutes(App& app) { // allowed only for admin BMCWEB_ROUTE(app, "/ibm/v1/") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request&, const std::shared_ptr& asyncResp) { asyncResp->res.jsonValue["@odata.type"] = "#ibmServiceRoot.v1_0_0.ibmServiceRoot"; asyncResp->res.jsonValue["@odata.id"] = "/ibm/v1/"; asyncResp->res.jsonValue["Id"] = "IBM Rest RootService"; asyncResp->res.jsonValue["Name"] = "IBM Service Root"; asyncResp->res.jsonValue["ConfigFiles"]["@odata.id"] = "/ibm/v1/Host/ConfigFiles"; asyncResp->res.jsonValue["BroadcastService"]["@odata.id"] = "/ibm/v1/HMC/BroadcastService"; }); BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::get)( [](const crow::Request&, const std::shared_ptr& asyncResp) { handleConfigFileList(asyncResp); }); BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles/Actions/IBMConfigFiles.DeleteAll") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::post)( [](const crow::Request&, const std::shared_ptr& asyncResp) { deleteConfigFiles(asyncResp); }); BMCWEB_ROUTE(app, "/ibm/v1/Host/ConfigFiles/") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::put, boost::beast::http::verb::get, boost::beast::http::verb::delete_)( [](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& fileName) { BMCWEB_LOG_DEBUG("ConfigFile : {}", fileName); // Validate the incoming fileName if (!isValidConfigFileName(fileName, asyncResp->res)) { asyncResp->res.result(boost::beast::http::status::bad_request); return; } handleFileUrl(req, asyncResp, fileName); }); BMCWEB_ROUTE(app, "/ibm/v1/HMC/BroadcastService") .privileges({{"ConfigureComponents", "ConfigureManager"}}) .methods(boost::beast::http::verb::post)( [](const crow::Request& req, const std::shared_ptr& asyncResp) { handleBroadcastService(req, asyncResp); }); } } // namespace ibm_mc } // namespace crow