/* // Copyright (c) 2018 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #include "user_mgmt.hpp" #include "apphandler.hpp" #include "channel_layer.hpp" #include "channel_mgmt.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace ipmi { // TODO: Move D-Bus & Object Manager related stuff, to common files // D-Bus property related static constexpr const char* dBusPropertiesInterface = "org.freedesktop.DBus.Properties"; static constexpr const char* getAllPropertiesMethod = "GetAll"; static constexpr const char* propertiesChangedSignal = "PropertiesChanged"; static constexpr const char* setPropertiesMethod = "Set"; // Object Manager related static constexpr const char* dBusObjManager = "org.freedesktop.DBus.ObjectManager"; static constexpr const char* getManagedObjectsMethod = "GetManagedObjects"; // Object Manager signals static constexpr const char* intfAddedSignal = "InterfacesAdded"; static constexpr const char* intfRemovedSignal = "InterfacesRemoved"; // Object Mapper related static constexpr const char* objMapperService = "xyz.openbmc_project.ObjectMapper"; static constexpr const char* objMapperPath = "/xyz/openbmc_project/object_mapper"; static constexpr const char* objMapperInterface = "xyz.openbmc_project.ObjectMapper"; static constexpr const char* getObjectMethod = "GetObject"; static constexpr const char* ipmiUserMutex = "ipmi_usr_mutex"; static constexpr const char* ipmiMutexCleanupLockFile = "/var/lib/ipmi/ipmi_usr_mutex_cleanup"; static constexpr const char* ipmiUserSignalLockFile = "/var/lib/ipmi/ipmi_usr_signal_mutex"; static constexpr const char* ipmiUserDataFile = "/var/lib/ipmi/ipmi_user.json"; static constexpr const char* ipmiGrpName = "ipmi"; static constexpr size_t privNoAccess = 0xF; static constexpr size_t privMask = 0xF; // User manager related static constexpr const char* userMgrObjBasePath = "/xyz/openbmc_project/user"; static constexpr const char* userObjBasePath = "/xyz/openbmc_project/user"; static constexpr const char* userMgrInterface = "xyz.openbmc_project.User.Manager"; static constexpr const char* usersInterface = "xyz.openbmc_project.User.Attributes"; static constexpr const char* deleteUserInterface = "xyz.openbmc_project.Object.Delete"; static constexpr const char* createUserMethod = "CreateUser"; static constexpr const char* deleteUserMethod = "Delete"; static constexpr const char* renameUserMethod = "RenameUser"; // User manager signal memebers static constexpr const char* userRenamedSignal = "UserRenamed"; // Mgr interface properties static constexpr const char* allPrivProperty = "AllPrivileges"; static constexpr const char* allGrpProperty = "AllGroups"; // User interface properties static constexpr const char* userPrivProperty = "UserPrivilege"; static constexpr const char* userGrpProperty = "UserGroups"; static constexpr const char* userEnabledProperty = "UserEnabled"; static std::array ipmiPrivIndex = { "priv-reserved", // PRIVILEGE_RESERVED - 0 "priv-callback", // PRIVILEGE_CALLBACK - 1 "priv-user", // PRIVILEGE_USER - 2 "priv-operator", // PRIVILEGE_OPERATOR - 3 "priv-admin", // PRIVILEGE_ADMIN - 4 "priv-custom" // PRIVILEGE_OEM - 5 }; using namespace phosphor::logging; using Json = nlohmann::json; using PrivAndGroupType = std::variant>; using NoResource = sdbusplus::error::xyz::openbmc_project::user::common::NoResource; using InternalFailure = sdbusplus::error::xyz::openbmc_project::common::InternalFailure; std::unique_ptr userUpdatedSignal __attribute__((init_priority(101))); std::unique_ptr userMgrRenamedSignal __attribute__((init_priority(101))); std::unique_ptr userPropertiesSignal __attribute__((init_priority(101))); // TODO: Below code can be removed once it is moved to common layer libmiscutil std::string getUserService(sdbusplus::bus_t& bus, const std::string& intf, const std::string& path) { auto mapperCall = bus.new_method_call(objMapperService, objMapperPath, objMapperInterface, getObjectMethod); mapperCall.append(path); mapperCall.append(std::vector({intf})); auto mapperResponseMsg = bus.call(mapperCall); std::map> mapperResponse; mapperResponseMsg.read(mapperResponse); if (mapperResponse.begin() == mapperResponse.end()) { throw sdbusplus::exception::SdBusError( -EIO, "ERROR in reading the mapper response"); } return mapperResponse.begin()->first; } void setDbusProperty(sdbusplus::bus_t& bus, const std::string& service, const std::string& objPath, const std::string& interface, const std::string& property, const DbusUserPropVariant& value) { try { auto method = bus.new_method_call(service.c_str(), objPath.c_str(), dBusPropertiesInterface, setPropertiesMethod); method.append(interface, property, value); bus.call(method); } catch (const sdbusplus::exception_t& e) { lg2::error("Failed to set {PROPERTY}, path: {PATH}, " "interface: {INTERFACE}", "PROPERTY", property, "PATH", objPath, "INTERFACE", interface); throw; } } std::string getUserServiceName() { static sdbusplus::bus_t bus(ipmid_get_sd_bus_connection()); static std::string userMgmtService; if (userMgmtService.empty()) { try { userMgmtService = ipmi::getUserService(bus, userMgrInterface, userMgrObjBasePath); } catch (const sdbusplus::exception_t& e) { userMgmtService.clear(); } } return userMgmtService; } UserAccess& getUserAccessObject() { static UserAccess userAccess; return userAccess; } int getUserNameFromPath(const std::string& path, std::string& userName) { sdbusplus::message::object_path objPath(path); userName.assign(objPath.filename()); return 0; } void userUpdateHelper(UserAccess& usrAccess, const UserUpdateEvent& userEvent, const std::string& userName, const std::string& priv, const bool& enabled, const std::string& newUserName) { UsersTbl* userData = usrAccess.getUsersTblPtr(); if (userEvent == UserUpdateEvent::userCreated) { if (usrAccess.addUserEntry(userName, priv, enabled) == false) { return; } } else { // user index 0 is reserved, starts with 1 size_t usrIndex = 1; for (; usrIndex <= ipmiMaxUsers; ++usrIndex) { std::string curName( reinterpret_cast(userData->user[usrIndex].userName), 0, ipmiMaxUserName); if (userName == curName) { break; // found the entry } } if (usrIndex > ipmiMaxUsers) { lg2::debug("User not found for signal, user name: {USER_NAME}, " "user event: {USER_EVENT}", "USER_NAME", userName, "USER_EVENT", userEvent); return; } switch (userEvent) { case UserUpdateEvent::userDeleted: { usrAccess.deleteUserIndex(usrIndex); break; } case UserUpdateEvent::userPrivUpdated: { uint8_t userPriv = static_cast( UserAccess::convertToIPMIPrivilege(priv)) & privMask; // Update all channels privileges, only if it is not equivalent // to getUsrMgmtSyncIndex() if (userData->user[usrIndex] .userPrivAccess[UserAccess::getUsrMgmtSyncIndex()] .privilege != userPriv) { for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex) { userData->user[usrIndex] .userPrivAccess[chIndex] .privilege = userPriv; } } break; } case UserUpdateEvent::userRenamed: { std::fill( static_cast(userData->user[usrIndex].userName), static_cast(userData->user[usrIndex].userName) + sizeof(userData->user[usrIndex].userName), 0); std::strncpy( reinterpret_cast(userData->user[usrIndex].userName), newUserName.c_str(), ipmiMaxUserName); ipmiRenameUserEntryPassword(userName, newUserName); break; } case UserUpdateEvent::userStateUpdated: { userData->user[usrIndex].userEnabled = enabled; break; } default: { lg2::error("Unhandled user event: {USER_EVENT}", "USER_EVENT", userEvent); return; } } } usrAccess.writeUserData(); lg2::debug("User event handled successfully, user name: {USER_NAME}, " "user event: {USER_EVENT}", "USER_NAME", userName.c_str(), "USER_EVENT", userEvent); return; } void userUpdatedSignalHandler(UserAccess& usrAccess, sdbusplus::message_t& msg) { static sdbusplus::bus_t bus(ipmid_get_sd_bus_connection()); std::string signal = msg.get_member(); std::string userName, priv, newUserName; std::vector groups; bool enabled = false; UserUpdateEvent userEvent = UserUpdateEvent::reservedEvent; if (signal == intfAddedSignal) { DbusUserObjPath objPath; DbusUserObjValue objValue; msg.read(objPath, objValue); getUserNameFromPath(objPath.str, userName); if (usrAccess.getUserObjProperties(objValue, groups, priv, enabled) != 0) { return; } if (std::find(groups.begin(), groups.end(), ipmiGrpName) == groups.end()) { return; } userEvent = UserUpdateEvent::userCreated; } else if (signal == intfRemovedSignal) { DbusUserObjPath objPath; std::vector interfaces; msg.read(objPath, interfaces); getUserNameFromPath(objPath.str, userName); userEvent = UserUpdateEvent::userDeleted; } else if (signal == userRenamedSignal) { msg.read(userName, newUserName); userEvent = UserUpdateEvent::userRenamed; } else if (signal == propertiesChangedSignal) { getUserNameFromPath(msg.get_path(), userName); } else { lg2::error("Unknown user update signal: {SIGNAL}", "SIGNAL", signal); return; } if (signal.empty() || userName.empty() || (signal == userRenamedSignal && newUserName.empty())) { lg2::error("Invalid inputs received"); return; } boost::interprocess::scoped_lock userLock{*(usrAccess.userMutex)}; usrAccess.checkAndReloadUserData(); if (signal == propertiesChangedSignal) { std::string intfName; DbusUserObjProperties chProperties; msg.read(intfName, chProperties); // skip reading 3rd argument. for (const auto& prop : chProperties) { userEvent = UserUpdateEvent::reservedEvent; std::string member = prop.first; if (member == userPrivProperty) { priv = std::get(prop.second); userEvent = UserUpdateEvent::userPrivUpdated; } else if (member == userGrpProperty) { groups = std::get>(prop.second); userEvent = UserUpdateEvent::userGrpUpdated; } else if (member == userEnabledProperty) { enabled = std::get(prop.second); userEvent = UserUpdateEvent::userStateUpdated; } // Process based on event type. if (userEvent == UserUpdateEvent::userGrpUpdated) { if (std::find(groups.begin(), groups.end(), ipmiGrpName) == groups.end()) { // remove user from ipmi user list. userUpdateHelper(usrAccess, UserUpdateEvent::userDeleted, userName, priv, enabled, newUserName); } else { DbusUserObjProperties properties; try { auto method = bus.new_method_call( getUserServiceName().c_str(), msg.get_path(), dBusPropertiesInterface, getAllPropertiesMethod); method.append(usersInterface); auto reply = bus.call(method); reply.read(properties); } catch (const sdbusplus::exception_t& e) { lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD", getAllPropertiesMethod, "PATH", msg.get_path()); return; } usrAccess.getUserProperties(properties, groups, priv, enabled); // add user to ipmi user list. userUpdateHelper(usrAccess, UserUpdateEvent::userCreated, userName, priv, enabled, newUserName); } } else if (userEvent != UserUpdateEvent::reservedEvent) { userUpdateHelper(usrAccess, userEvent, userName, priv, enabled, newUserName); } } } else if (userEvent != UserUpdateEvent::reservedEvent) { userUpdateHelper(usrAccess, userEvent, userName, priv, enabled, newUserName); } return; } UserAccess::~UserAccess() { if (signalHndlrObject) { userUpdatedSignal.reset(); userMgrRenamedSignal.reset(); userPropertiesSignal.reset(); sigHndlrLock.unlock(); } } UserAccess::UserAccess() : bus(ipmid_get_sd_bus_connection()) { std::ofstream mutexCleanUpFile; mutexCleanUpFile.open(ipmiMutexCleanupLockFile, std::ofstream::out | std::ofstream::app); if (!mutexCleanUpFile.good()) { lg2::debug("Unable to open mutex cleanup file"); return; } mutexCleanUpFile.close(); mutexCleanupLock = boost::interprocess::file_lock(ipmiMutexCleanupLockFile); if (mutexCleanupLock.try_lock()) { boost::interprocess::named_recursive_mutex::remove(ipmiUserMutex); } mutexCleanupLock.lock_sharable(); userMutex = std::make_unique( boost::interprocess::open_or_create, ipmiUserMutex); cacheUserDataFile(); getSystemPrivAndGroups(); } UserInfo* UserAccess::getUserInfo(const uint8_t userId) { checkAndReloadUserData(); return &usersTbl.user[userId]; } void UserAccess::setUserInfo(const uint8_t userId, UserInfo* userInfo) { checkAndReloadUserData(); std::copy(reinterpret_cast(userInfo), reinterpret_cast(userInfo) + sizeof(*userInfo), reinterpret_cast(&usersTbl.user[userId])); writeUserData(); } bool UserAccess::isValidChannel(const uint8_t chNum) { return (chNum < ipmiMaxChannels); } bool UserAccess::isValidUserId(const uint8_t userId) { return ((userId <= ipmiMaxUsers) && (userId != reservedUserId)); } bool UserAccess::isValidPrivilege(const uint8_t priv) { // Callback privilege is deprecated in OpenBMC return isValidPrivLimit(priv); } uint8_t UserAccess::getUsrMgmtSyncIndex() { // Identify the IPMI channel used to assign system user privilege levels // in phosphor-user-manager. The default value is IPMI Channel 1. To // assign a different channel add: // "is_management_nic" : true // into the channel_config.json file describing the assignment of the IPMI // channels. It is only necessary to add the string above to ONE record in // the channel_config.json file. All other records will be automatically // assigned a "false" value. return getChannelConfigObject().getManagementNICID(); } CommandPrivilege UserAccess::convertToIPMIPrivilege(const std::string& value) { auto iter = std::find(ipmiPrivIndex.begin(), ipmiPrivIndex.end(), value); if (iter == ipmiPrivIndex.end()) { if (value == "") { return static_cast(privNoAccess); } lg2::error("Error in converting to IPMI privilege: {PRIV}", "PRIV", value); throw std::out_of_range("Out of range - convertToIPMIPrivilege"); } else { return static_cast( std::distance(ipmiPrivIndex.begin(), iter)); } } std::string UserAccess::convertToSystemPrivilege(const CommandPrivilege& value) { if (value == static_cast(privNoAccess)) { return ""; } try { return ipmiPrivIndex.at(value); } catch (const std::out_of_range& e) { lg2::error("Error in converting to system privilege: {PRIV}", "PRIV", value); throw std::out_of_range("Out of range - convertToSystemPrivilege"); } } bool UserAccess::isValidUserName(const std::string& userName) { if (userName.empty()) { lg2::error("userName is empty"); return false; } if (!std::regex_match(userName.c_str(), std::regex("[a-zA-Z_][a-zA-Z_0-9]*"))) { lg2::error("Unsupported characters in user name"); return false; } if (userName == "root") { lg2::error("Invalid user name - root"); return false; } std::map properties; try { auto method = bus.new_method_call(getUserServiceName().c_str(), userMgrObjBasePath, dBusObjManager, getManagedObjectsMethod); auto reply = bus.call(method); reply.read(properties); } catch (const sdbusplus::exception_t& e) { lg2::error("Failed to excute {METHOD}, path: {PATH}", "METHOD", getManagedObjectsMethod, "PATH", userMgrObjBasePath); return false; } sdbusplus::message::object_path tempUserPath(userObjBasePath); tempUserPath /= userName; std::string usersPath(tempUserPath); if (properties.find(usersPath) != properties.end()) { lg2::debug("Username {USER_NAME} already exists", "USER_NAME", userName); return false; } return true; } /** @brief Information exchanged by pam module and application. * * @param[in] numMsg - length of the array of pointers,msg. * * @param[in] msg - pointer to an array of pointers to pam_message structure * * @param[out] resp - struct pam response array * * @param[in] appdataPtr - member of pam_conv structure * * @return the response in pam response structure. */ static int pamFunctionConversation(int numMsg, const struct pam_message** msg, struct pam_response** resp, void* appdataPtr) { if (appdataPtr == nullptr) { return PAM_CONV_ERR; } if (numMsg <= 0 || numMsg >= PAM_MAX_NUM_MSG) { return PAM_CONV_ERR; } for (int i = 0; i < numMsg; ++i) { /* Ignore all PAM messages except prompting for hidden input */ if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF) { continue; } /* Assume PAM is only prompting for the password as hidden input */ /* Allocate memory only when PAM_PROMPT_ECHO_OFF is encounterred */ char* appPass = reinterpret_cast(appdataPtr); size_t appPassSize = std::strlen(appPass); if (appPassSize >= PAM_MAX_RESP_SIZE) { return PAM_CONV_ERR; } char* pass = reinterpret_cast(malloc(appPassSize + 1)); if (pass == nullptr) { return PAM_BUF_ERR; } void* ptr = calloc(static_cast(numMsg), sizeof(struct pam_response)); if (ptr == nullptr) { free(pass); return PAM_BUF_ERR; } std::strncpy(pass, appPass, appPassSize + 1); *resp = reinterpret_cast(ptr); resp[i]->resp = pass; return PAM_SUCCESS; } return PAM_CONV_ERR; } /** @brief Updating the PAM password * * @param[in] username - username in string * * @param[in] password - new password in string * * @return status */ int pamUpdatePasswd(const char* username, const char* password) { const struct pam_conv localConversation = {pamFunctionConversation, const_cast(password)}; pam_handle_t* localAuthHandle = NULL; // this gets set by pam_start int retval = pam_start("passwd", username, &localConversation, &localAuthHandle); if (retval != PAM_SUCCESS) { return retval; } retval = pam_chauthtok(localAuthHandle, PAM_SILENT); if (retval != PAM_SUCCESS) { pam_end(localAuthHandle, retval); return retval; } return pam_end(localAuthHandle, PAM_SUCCESS); } bool pamUserCheckAuthenticate(std::string_view username, std::string_view password) { const struct pam_conv localConversation = { pamFunctionConversation, const_cast(password.data())}; pam_handle_t* localAuthHandle = NULL; // this gets set by pam_start if (pam_start("dropbear", username.data(), &localConversation, &localAuthHandle) != PAM_SUCCESS) { lg2::error("User Authentication Failure"); return false; } int retval = pam_authenticate(localAuthHandle, PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK); if (retval != PAM_SUCCESS) { lg2::debug("pam_authenticate returned failure: {ERROR}", "ERROR", retval); pam_end(localAuthHandle, retval); return false; } if (pam_acct_mgmt(localAuthHandle, PAM_DISALLOW_NULL_AUTHTOK) != PAM_SUCCESS) { pam_end(localAuthHandle, PAM_SUCCESS); return false; } if (pam_end(localAuthHandle, PAM_SUCCESS) != PAM_SUCCESS) { return false; } return true; } Cc UserAccess::setSpecialUserPassword(const std::string& userName, const SecureString& userPassword) { if (pamUpdatePasswd(userName.c_str(), userPassword.c_str()) != PAM_SUCCESS) { lg2::debug("Failed to update password"); return ccUnspecifiedError; } return ccSuccess; } Cc UserAccess::setUserPassword(const uint8_t userId, const char* userPassword) { std::string userName; if (ipmiUserGetUserName(userId, userName) != ccSuccess) { lg2::debug("User Name not found, user Id: {USER_ID}", "USER_ID", userId); return ccParmOutOfRange; } ipmi::SecureString passwd; passwd.assign(reinterpret_cast(userPassword), 0, maxIpmi20PasswordSize); int retval = pamUpdatePasswd(userName.c_str(), passwd.c_str()); switch (retval) { case PAM_SUCCESS: { return ccSuccess; } case PAM_AUTHTOK_ERR: { lg2::debug("Bad authentication token"); return ccInvalidFieldRequest; } default: { lg2::debug("Failed to update password, user Id: {USER_ID}", "USER_ID", userId); return ccUnspecifiedError; } } } Cc UserAccess::setUserEnabledState(const uint8_t userId, const bool& enabledState) { if (!isValidUserId(userId)) { return ccParmOutOfRange; } boost::interprocess::scoped_lock userLock{*userMutex}; UserInfo* userInfo = getUserInfo(userId); std::string userName; userName.assign(reinterpret_cast(userInfo->userName), 0, ipmiMaxUserName); if (userName.empty()) { lg2::debug("User name not set / invalid"); return ccUnspecifiedError; } if (userInfo->userEnabled != enabledState) { sdbusplus::message::object_path tempUserPath(userObjBasePath); tempUserPath /= userName; std::string userPath(tempUserPath); setDbusProperty(bus, getUserServiceName(), userPath, usersInterface, userEnabledProperty, enabledState); userInfo->userEnabled = enabledState; try { writeUserData(); } catch (const std::exception& e) { lg2::debug("Write user data failed"); return ccUnspecifiedError; } } return ccSuccess; } Cc UserAccess::setUserPayloadAccess(const uint8_t chNum, const uint8_t operation, const uint8_t userId, const PayloadAccess& payloadAccess) { constexpr uint8_t enable = 0x0; constexpr uint8_t disable = 0x1; if (!isValidChannel(chNum)) { return ccInvalidFieldRequest; } if (!isValidUserId(userId)) { return ccParmOutOfRange; } if (operation != enable && operation != disable) { return ccInvalidFieldRequest; } // Check operation & payloadAccess if required. boost::interprocess::scoped_lock userLock{*userMutex}; UserInfo* userInfo = getUserInfo(userId); if (operation == enable) { userInfo->payloadAccess[chNum].stdPayloadEnables1 |= payloadAccess.stdPayloadEnables1; userInfo->payloadAccess[chNum].oemPayloadEnables1 |= payloadAccess.oemPayloadEnables1; } else { userInfo->payloadAccess[chNum].stdPayloadEnables1 &= ~(payloadAccess.stdPayloadEnables1); userInfo->payloadAccess[chNum].oemPayloadEnables1 &= ~(payloadAccess.oemPayloadEnables1); } try { writeUserData(); } catch (const std::exception& e) { lg2::error("Write user data failed"); return ccUnspecifiedError; } return ccSuccess; } Cc UserAccess::setUserPrivilegeAccess(const uint8_t userId, const uint8_t chNum, const UserPrivAccess& privAccess, const bool& otherPrivUpdates) { if (!isValidChannel(chNum)) { return ccInvalidFieldRequest; } if (!isValidUserId(userId)) { return ccParmOutOfRange; } boost::interprocess::scoped_lock userLock{*userMutex}; UserInfo* userInfo = getUserInfo(userId); std::string userName; userName.assign(reinterpret_cast(userInfo->userName), 0, ipmiMaxUserName); if (userName.empty()) { lg2::debug("User name not set / invalid"); return ccUnspecifiedError; } std::string priv = convertToSystemPrivilege( static_cast(privAccess.privilege)); uint8_t syncIndex = getUsrMgmtSyncIndex(); if (chNum == syncIndex && privAccess.privilege != userInfo->userPrivAccess[syncIndex].privilege) { sdbusplus::message::object_path tempUserPath(userObjBasePath); tempUserPath /= userName; std::string userPath(tempUserPath); setDbusProperty(bus, getUserServiceName(), userPath, usersInterface, userPrivProperty, priv); } userInfo->userPrivAccess[chNum].privilege = privAccess.privilege; if (otherPrivUpdates) { userInfo->userPrivAccess[chNum].ipmiEnabled = privAccess.ipmiEnabled; userInfo->userPrivAccess[chNum].linkAuthEnabled = privAccess.linkAuthEnabled; userInfo->userPrivAccess[chNum].accessCallback = privAccess.accessCallback; } try { writeUserData(); } catch (const std::exception& e) { lg2::debug("Write user data failed"); return ccUnspecifiedError; } return ccSuccess; } uint8_t UserAccess::getUserId(const std::string& userName) { boost::interprocess::scoped_lock userLock{*userMutex}; checkAndReloadUserData(); // user index 0 is reserved, starts with 1 size_t usrIndex = 1; for (; usrIndex <= ipmiMaxUsers; ++usrIndex) { std::string curName( reinterpret_cast(usersTbl.user[usrIndex].userName), 0, ipmiMaxUserName); if (userName == curName) { break; // found the entry } } if (usrIndex > ipmiMaxUsers) { lg2::debug("Username {USER_NAME} not found", "USER_NAME", userName); return invalidUserId; } return usrIndex; } Cc UserAccess::getUserName(const uint8_t userId, std::string& userName) { if (!isValidUserId(userId)) { return ccParmOutOfRange; } UserInfo* userInfo = getUserInfo(userId); userName.assign(reinterpret_cast(userInfo->userName), 0, ipmiMaxUserName); return ccSuccess; } bool UserAccess::isIpmiInAvailableGroupList() { if (std::find(availableGroups.begin(), availableGroups.end(), ipmiGrpName) != availableGroups.end()) { return true; } if (availableGroups.empty()) { // available groups shouldn't be empty, re-query getSystemPrivAndGroups(); if (std::find(availableGroups.begin(), availableGroups.end(), ipmiGrpName) != availableGroups.end()) { return true; } } return false; } Cc UserAccess::setUserName(const uint8_t userId, const std::string& userName) { if (!isValidUserId(userId)) { return ccParmOutOfRange; } boost::interprocess::scoped_lock userLock{*userMutex}; std::string oldUser; getUserName(userId, oldUser); if (oldUser == userName) { // requesting to set the same user name, return success. return ccSuccess; } bool validUser = isValidUserName(userName); UserInfo* userInfo = getUserInfo(userId); if (userName.empty() && !oldUser.empty()) { // Delete existing user sdbusplus::message::object_path tempUserPath(userObjBasePath); tempUserPath /= oldUser; std::string userPath(tempUserPath); try { auto method = bus.new_method_call( getUserServiceName().c_str(), userPath.c_str(), deleteUserInterface, deleteUserMethod); auto reply = bus.call(method); } catch (const sdbusplus::exception_t& e) { lg2::debug("Failed to excute {METHOD}, path:{PATH}", "METHOD", deleteUserMethod, "PATH", userPath); return ccUnspecifiedError; } deleteUserIndex(userId); } else if (oldUser.empty() && !userName.empty() && validUser) { try { if (!isIpmiInAvailableGroupList()) { return ccUnspecifiedError; } // Create new user auto method = bus.new_method_call( getUserServiceName().c_str(), userMgrObjBasePath, userMgrInterface, createUserMethod); method.append(userName.c_str(), availableGroups, ipmiPrivIndex[PRIVILEGE_USER], false); auto reply = bus.call(method); } catch (const sdbusplus::exception_t& e) { lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD", createUserMethod, "PATH", userMgrObjBasePath); return ccUnspecifiedError; } std::memset(userInfo->userName, 0, sizeof(userInfo->userName)); std::memcpy(userInfo->userName, static_cast(userName.data()), userName.size()); userInfo->userInSystem = true; for (size_t chIndex = 0; chIndex < ipmiMaxChannels; chIndex++) { userInfo->userPrivAccess[chIndex].privilege = static_cast(PRIVILEGE_USER); } } else if (oldUser != userName && validUser) { try { // User rename auto method = bus.new_method_call( getUserServiceName().c_str(), userMgrObjBasePath, userMgrInterface, renameUserMethod); method.append(oldUser.c_str(), userName.c_str()); auto reply = bus.call(method); } catch (const sdbusplus::exception_t& e) { lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD", renameUserMethod, "PATH", userMgrObjBasePath); return ccUnspecifiedError; } std::fill(static_cast(userInfo->userName), static_cast(userInfo->userName) + sizeof(userInfo->userName), 0); std::memset(userInfo->userName, 0, sizeof(userInfo->userName)); std::memcpy(userInfo->userName, static_cast(userName.data()), userName.size()); ipmiRenameUserEntryPassword(oldUser, userName); userInfo->userInSystem = true; } else if (!validUser) { return ccInvalidFieldRequest; } try { writeUserData(); } catch (const std::exception& e) { lg2::debug("Write user data failed"); return ccUnspecifiedError; } return ccSuccess; } static constexpr const char* jsonUserName = "user_name"; static constexpr const char* jsonPriv = "privilege"; static constexpr const char* jsonIpmiEnabled = "ipmi_enabled"; static constexpr const char* jsonLinkAuthEnabled = "link_auth_enabled"; static constexpr const char* jsonAccCallbk = "access_callback"; static constexpr const char* jsonUserEnabled = "user_enabled"; static constexpr const char* jsonUserInSys = "user_in_system"; static constexpr const char* jsonFixedUser = "fixed_user_name"; static constexpr const char* payloadEnabledStr = "payload_enabled"; static constexpr const char* stdPayloadStr = "std_payload"; static constexpr const char* oemPayloadStr = "OEM_payload"; /** @brief to construct a JSON object from the given payload access details. * * @param[in] stdPayload - stdPayloadEnables1 in a 2D-array. (input) * @param[in] oemPayload - oemPayloadEnables1 in a 2D-array. (input) * * @details Sample output JSON object format : * "payload_enabled":{ * "OEM_payload0":[false,...], * "OEM_payload1":[false,...], * "OEM_payload2":[false,...], * "OEM_payload3":[false,...], * "OEM_payload4":[false,...], * "OEM_payload5":[false,...], * "OEM_payload6":[false,...], * "OEM_payload7":[false,...], * "std_payload0":[false,...], * "std_payload1":[false,...], * "std_payload2":[false,...], * "std_payload3":[false,...], * "std_payload4":[false,...], * "std_payload5":[false,...], * "std_payload6":[false,...], * "std_payload7":[false,...], * } */ static const Json constructJsonPayloadEnables( const std::array, payloadsPerByte>& stdPayload, const std::array, payloadsPerByte>& oemPayload) { Json jsonPayloadEnabled; for (auto payloadNum = 0; payloadNum < payloadsPerByte; payloadNum++) { std::ostringstream stdPayloadStream; std::ostringstream oemPayloadStream; stdPayloadStream << stdPayloadStr << payloadNum; oemPayloadStream << oemPayloadStr << payloadNum; jsonPayloadEnabled.push_back(Json::object_t::value_type( stdPayloadStream.str(), stdPayload[payloadNum])); jsonPayloadEnabled.push_back(Json::object_t::value_type( oemPayloadStream.str(), oemPayload[payloadNum])); } return jsonPayloadEnabled; } void UserAccess::readPayloadAccessFromUserInfo( const UserInfo& userInfo, std::array, payloadsPerByte>& stdPayload, std::array, payloadsPerByte>& oemPayload) { for (auto payloadNum = 0; payloadNum < payloadsPerByte; payloadNum++) { for (auto chIndex = 0; chIndex < ipmiMaxChannels; chIndex++) { stdPayload[payloadNum][chIndex] = userInfo.payloadAccess[chIndex].stdPayloadEnables1[payloadNum]; oemPayload[payloadNum][chIndex] = userInfo.payloadAccess[chIndex].oemPayloadEnables1[payloadNum]; } } } void UserAccess::updatePayloadAccessInUserInfo( const std::array, payloadsPerByte>& stdPayload, const std::array, payloadsPerByte>&, UserInfo& userInfo) { for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex) { // Ensure that reserved/unsupported payloads are marked to zero. userInfo.payloadAccess[chIndex].stdPayloadEnables1.reset(); userInfo.payloadAccess[chIndex].oemPayloadEnables1.reset(); userInfo.payloadAccess[chIndex].stdPayloadEnables2Reserved.reset(); userInfo.payloadAccess[chIndex].oemPayloadEnables2Reserved.reset(); // Update SOL status as it is the only supported payload currently. userInfo.payloadAccess[chIndex] .stdPayloadEnables1[static_cast(ipmi::PayloadType::SOL)] = stdPayload[static_cast(ipmi::PayloadType::SOL)][chIndex]; } } void UserAccess::readUserData() { boost::interprocess::scoped_lock userLock{*userMutex}; std::ifstream iUsrData(ipmiUserDataFile, std::ios::in | std::ios::binary); if (!iUsrData.good()) { lg2::error("Error in reading IPMI user data file"); throw std::ios_base::failure("Error opening IPMI user data file"); } Json jsonUsersTbl = Json::array(); jsonUsersTbl = Json::parse(iUsrData, nullptr, false); if (jsonUsersTbl.size() != ipmiMaxUsers) { lg2::error("Error in reading IPMI user data file - User count issues"); throw std::runtime_error( "Corrupted IPMI user data file - invalid user count"); } // user index 0 is reserved, starts with 1 for (size_t usrIndex = 1; usrIndex <= ipmiMaxUsers; ++usrIndex) { Json userInfo = jsonUsersTbl[usrIndex - 1]; // json array starts with 0. if (userInfo.is_null()) { lg2::error("Error in reading IPMI user data file - " "user info corrupted"); throw std::runtime_error( "Corrupted IPMI user data file - invalid user info"); } std::string userName = userInfo[jsonUserName].get(); std::strncpy(reinterpret_cast(usersTbl.user[usrIndex].userName), userName.c_str(), ipmiMaxUserName); std::vector privilege = userInfo[jsonPriv].get>(); std::vector ipmiEnabled = userInfo[jsonIpmiEnabled].get>(); std::vector linkAuthEnabled = userInfo[jsonLinkAuthEnabled].get>(); std::vector accessCallback = userInfo[jsonAccCallbk].get>(); // Payload Enables Processing. std::array, payloadsPerByte> stdPayload = {}; std::array, payloadsPerByte> oemPayload = {}; try { const auto jsonPayloadEnabled = userInfo.at(payloadEnabledStr); for (auto payloadNum = 0; payloadNum < payloadsPerByte; payloadNum++) { std::ostringstream stdPayloadStream; std::ostringstream oemPayloadStream; stdPayloadStream << stdPayloadStr << payloadNum; oemPayloadStream << oemPayloadStr << payloadNum; stdPayload[payloadNum] = jsonPayloadEnabled[stdPayloadStream.str()] .get>(); oemPayload[payloadNum] = jsonPayloadEnabled[oemPayloadStream.str()] .get>(); if (stdPayload[payloadNum].size() != ipmiMaxChannels || oemPayload[payloadNum].size() != ipmiMaxChannels) { lg2::error("Error in reading IPMI user data file - " "payload properties corrupted"); throw std::runtime_error( "Corrupted IPMI user data file - payload properties"); } } } catch (const Json::out_of_range& e) { // Key not found in 'userInfo'; possibly an old JSON file. Use // default values for all payloads, and SOL payload default is true. stdPayload[static_cast(ipmi::PayloadType::SOL)].fill(true); } if (privilege.size() != ipmiMaxChannels || ipmiEnabled.size() != ipmiMaxChannels || linkAuthEnabled.size() != ipmiMaxChannels || accessCallback.size() != ipmiMaxChannels) { lg2::error("Error in reading IPMI user data file - " "properties corrupted"); throw std::runtime_error( "Corrupted IPMI user data file - properties"); } for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex) { usersTbl.user[usrIndex].userPrivAccess[chIndex].privilege = static_cast( convertToIPMIPrivilege(privilege[chIndex])); usersTbl.user[usrIndex].userPrivAccess[chIndex].ipmiEnabled = ipmiEnabled[chIndex]; usersTbl.user[usrIndex].userPrivAccess[chIndex].linkAuthEnabled = linkAuthEnabled[chIndex]; usersTbl.user[usrIndex].userPrivAccess[chIndex].accessCallback = accessCallback[chIndex]; } updatePayloadAccessInUserInfo(stdPayload, oemPayload, usersTbl.user[usrIndex]); usersTbl.user[usrIndex].userEnabled = userInfo[jsonUserEnabled].get(); usersTbl.user[usrIndex].userInSystem = userInfo[jsonUserInSys].get(); usersTbl.user[usrIndex].fixedUserName = userInfo[jsonFixedUser].get(); } lg2::debug("User data read from IPMI data file"); iUsrData.close(); // Update the timestamp fileLastUpdatedTime = getUpdatedFileTime(); return; } void UserAccess::writeUserData() { boost::interprocess::scoped_lock userLock{*userMutex}; Json jsonUsersTbl = Json::array(); // user index 0 is reserved, starts with 1 for (size_t usrIndex = 1; usrIndex <= ipmiMaxUsers; ++usrIndex) { Json jsonUserInfo; jsonUserInfo[jsonUserName] = std::string( reinterpret_cast(usersTbl.user[usrIndex].userName), 0, ipmiMaxUserName); std::vector privilege(ipmiMaxChannels); std::vector ipmiEnabled(ipmiMaxChannels); std::vector linkAuthEnabled(ipmiMaxChannels); std::vector accessCallback(ipmiMaxChannels); std::array, payloadsPerByte> stdPayload; std::array, payloadsPerByte> oemPayload; for (size_t chIndex = 0; chIndex < ipmiMaxChannels; chIndex++) { privilege[chIndex] = convertToSystemPrivilege(static_cast( usersTbl.user[usrIndex].userPrivAccess[chIndex].privilege)); ipmiEnabled[chIndex] = usersTbl.user[usrIndex].userPrivAccess[chIndex].ipmiEnabled; linkAuthEnabled[chIndex] = usersTbl.user[usrIndex].userPrivAccess[chIndex].linkAuthEnabled; accessCallback[chIndex] = usersTbl.user[usrIndex].userPrivAccess[chIndex].accessCallback; } jsonUserInfo[jsonPriv] = privilege; jsonUserInfo[jsonIpmiEnabled] = ipmiEnabled; jsonUserInfo[jsonLinkAuthEnabled] = linkAuthEnabled; jsonUserInfo[jsonAccCallbk] = accessCallback; jsonUserInfo[jsonUserEnabled] = usersTbl.user[usrIndex].userEnabled; jsonUserInfo[jsonUserInSys] = usersTbl.user[usrIndex].userInSystem; jsonUserInfo[jsonFixedUser] = usersTbl.user[usrIndex].fixedUserName; readPayloadAccessFromUserInfo(usersTbl.user[usrIndex], stdPayload, oemPayload); Json jsonPayloadEnabledInfo = constructJsonPayloadEnables(stdPayload, oemPayload); jsonUserInfo[payloadEnabledStr] = jsonPayloadEnabledInfo; jsonUsersTbl.push_back(jsonUserInfo); } static std::string tmpFile{std::string(ipmiUserDataFile) + "_tmp"}; int fd = open(tmpFile.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { lg2::error("Error in creating temporary IPMI user data file"); throw std::ios_base::failure( "Error in creating temporary IPMI user data file"); } const auto& writeStr = jsonUsersTbl.dump(); if (write(fd, writeStr.c_str(), writeStr.size()) != static_cast(writeStr.size())) { close(fd); lg2::error("Error in writing temporary IPMI user data file"); throw std::ios_base::failure( "Error in writing temporary IPMI user data file"); } close(fd); if (std::rename(tmpFile.c_str(), ipmiUserDataFile) != 0) { lg2::error("Error in renaming temporary IPMI user data file"); throw std::runtime_error("Error in renaming IPMI user data file"); } // Update the timestamp fileLastUpdatedTime = getUpdatedFileTime(); return; } bool UserAccess::addUserEntry(const std::string& userName, const std::string& sysPriv, const bool& enabled) { UsersTbl* userData = getUsersTblPtr(); size_t freeIndex = 0xFF; // user index 0 is reserved, starts with 1 for (size_t usrIndex = 1; usrIndex <= ipmiMaxUsers; ++usrIndex) { std::string curName( reinterpret_cast(userData->user[usrIndex].userName), 0, ipmiMaxUserName); if (userName == curName) { lg2::debug("Username {USER_NAME} exists", "USER_NAME", userName); return false; // user name exists. } if ((!userData->user[usrIndex].userInSystem) && (userData->user[usrIndex].userName[0] == '\0') && (freeIndex == 0xFF)) { freeIndex = usrIndex; } } if (freeIndex == 0xFF) { lg2::error("No empty slots found"); return false; } std::strncpy(reinterpret_cast(userData->user[freeIndex].userName), userName.c_str(), ipmiMaxUserName); uint8_t priv = static_cast(UserAccess::convertToIPMIPrivilege(sysPriv)) & privMask; for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex) { userData->user[freeIndex].userPrivAccess[chIndex].privilege = priv; userData->user[freeIndex].userPrivAccess[chIndex].ipmiEnabled = true; userData->user[freeIndex].userPrivAccess[chIndex].linkAuthEnabled = true; userData->user[freeIndex].userPrivAccess[chIndex].accessCallback = true; } userData->user[freeIndex].userInSystem = true; userData->user[freeIndex].userEnabled = enabled; return true; } void UserAccess::deleteUserIndex(const size_t& usrIdx) { UsersTbl* userData = getUsersTblPtr(); std::string userName( reinterpret_cast(userData->user[usrIdx].userName), 0, ipmiMaxUserName); ipmiClearUserEntryPassword(userName); std::fill(static_cast(userData->user[usrIdx].userName), static_cast(userData->user[usrIdx].userName) + sizeof(userData->user[usrIdx].userName), 0); for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex) { userData->user[usrIdx].userPrivAccess[chIndex].privilege = privNoAccess; userData->user[usrIdx].userPrivAccess[chIndex].ipmiEnabled = false; userData->user[usrIdx].userPrivAccess[chIndex].linkAuthEnabled = false; userData->user[usrIdx].userPrivAccess[chIndex].accessCallback = false; } userData->user[usrIdx].userInSystem = false; userData->user[usrIdx].userEnabled = false; return; } void UserAccess::checkAndReloadUserData() { std::timespec updateTime = getUpdatedFileTime(); if ((updateTime.tv_sec != fileLastUpdatedTime.tv_sec || updateTime.tv_nsec != fileLastUpdatedTime.tv_nsec) || (updateTime.tv_sec == 0 && updateTime.tv_nsec == 0)) { std::fill(reinterpret_cast(&usersTbl), reinterpret_cast(&usersTbl) + sizeof(usersTbl), 0); readUserData(); } return; } UsersTbl* UserAccess::getUsersTblPtr() { // reload data before using it. checkAndReloadUserData(); return &usersTbl; } void UserAccess::getSystemPrivAndGroups() { std::map properties; try { auto method = bus.new_method_call( getUserServiceName().c_str(), userMgrObjBasePath, dBusPropertiesInterface, getAllPropertiesMethod); method.append(userMgrInterface); auto reply = bus.call(method); reply.read(properties); } catch (const sdbusplus::exception_t& e) { lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD", getAllPropertiesMethod, "PATH", userMgrObjBasePath); return; } for (const auto& t : properties) { auto key = t.first; if (key == allPrivProperty) { availablePrivileges = std::get>(t.second); } else if (key == allGrpProperty) { availableGroups = std::get>(t.second); } } // TODO: Implement Supported Privilege & Groups verification logic return; } std::timespec UserAccess::getUpdatedFileTime() { struct stat fileStat; if (stat(ipmiUserDataFile, &fileStat) != 0) { lg2::debug("Error in getting last updated time stamp"); return std::timespec{0, 0}; } return fileStat.st_mtim; } void UserAccess::getUserProperties(const DbusUserObjProperties& properties, std::vector& usrGrps, std::string& usrPriv, bool& usrEnabled) { for (const auto& t : properties) { std::string key = t.first; if (key == userPrivProperty) { usrPriv = std::get(t.second); } else if (key == userGrpProperty) { usrGrps = std::get>(t.second); } else if (key == userEnabledProperty) { usrEnabled = std::get(t.second); } } return; } int UserAccess::getUserObjProperties(const DbusUserObjValue& userObjs, std::vector& usrGrps, std::string& usrPriv, bool& usrEnabled) { auto usrObj = userObjs.find(usersInterface); if (usrObj != userObjs.end()) { getUserProperties(usrObj->second, usrGrps, usrPriv, usrEnabled); return 0; } return -EIO; } void UserAccess::cacheUserDataFile() { boost::interprocess::scoped_lock userLock{*userMutex}; try { readUserData(); } catch (const std::ios_base::failure& e) { // File is empty, create it for the first time std::fill(reinterpret_cast(&usersTbl), reinterpret_cast(&usersTbl) + sizeof(usersTbl), 0); // user index 0 is reserved, starts with 1 for (size_t userIndex = 1; userIndex <= ipmiMaxUsers; ++userIndex) { for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex) { usersTbl.user[userIndex].userPrivAccess[chIndex].privilege = privNoAccess; usersTbl.user[userIndex] .payloadAccess[chIndex] .stdPayloadEnables1[static_cast( ipmi::PayloadType::SOL)] = true; } } writeUserData(); } // Create lock file if it does not exist int fd = open(ipmiUserSignalLockFile, O_CREAT | O_TRUNC | O_SYNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { lg2::error("Error in creating IPMI user signal lock file"); throw std::ios_base::failure( "Error in creating temporary IPMI user signal lock file"); } close(fd); sigHndlrLock = boost::interprocess::file_lock(ipmiUserSignalLockFile); // Register it for single object and single process either netipmid / // host-ipmid if (userUpdatedSignal == nullptr && sigHndlrLock.try_lock()) { lg2::debug("Registering signal handler"); userUpdatedSignal = std::make_unique( bus, sdbusplus::bus::match::rules::type::signal() + sdbusplus::bus::match::rules::interface(dBusObjManager) + sdbusplus::bus::match::rules::path(userMgrObjBasePath), [&](sdbusplus::message_t& msg) { userUpdatedSignalHandler(*this, msg); }); userMgrRenamedSignal = std::make_unique( bus, sdbusplus::bus::match::rules::type::signal() + sdbusplus::bus::match::rules::interface(userMgrInterface) + sdbusplus::bus::match::rules::path(userMgrObjBasePath), [&](sdbusplus::message_t& msg) { userUpdatedSignalHandler(*this, msg); }); userPropertiesSignal = std::make_unique( bus, sdbusplus::bus::match::rules::type::signal() + sdbusplus::bus::match::rules::path_namespace(userObjBasePath) + sdbusplus::bus::match::rules::interface( dBusPropertiesInterface) + sdbusplus::bus::match::rules::member(propertiesChangedSignal) + sdbusplus::bus::match::rules::argN(0, usersInterface), [&](sdbusplus::message_t& msg) { userUpdatedSignalHandler(*this, msg); }); signalHndlrObject = true; } std::map managedObjs; try { auto method = bus.new_method_call(getUserServiceName().c_str(), userMgrObjBasePath, dBusObjManager, getManagedObjectsMethod); auto reply = bus.call(method); reply.read(managedObjs); } catch (const sdbusplus::exception_t& e) { lg2::debug("Failed to excute {METHOD}, path: {PATH}", "METHOD", getManagedObjectsMethod, "PATH", userMgrObjBasePath); return; } bool updateRequired = false; UsersTbl* userData = &usersTbl; // user index 0 is reserved, starts with 1 for (size_t usrIdx = 1; usrIdx <= ipmiMaxUsers; ++usrIdx) { if ((userData->user[usrIdx].userInSystem) && (userData->user[usrIdx].userName[0] != '\0')) { std::vector usrGrps; std::string usrPriv; std::string userName( reinterpret_cast(userData->user[usrIdx].userName), 0, ipmiMaxUserName); sdbusplus::message::object_path tempUserPath(userObjBasePath); tempUserPath /= userName; std::string usersPath(tempUserPath); auto usrObj = managedObjs.find(usersPath); if (usrObj != managedObjs.end()) { bool usrEnabled = false; // User exist. Lets check and update other fileds getUserObjProperties(usrObj->second, usrGrps, usrPriv, usrEnabled); if (std::find(usrGrps.begin(), usrGrps.end(), ipmiGrpName) == usrGrps.end()) { updateRequired = true; // Group "ipmi" is removed so lets remove user in IPMI deleteUserIndex(usrIdx); } else { // Group "ipmi" is present so lets update other properties // in IPMI uint8_t priv = UserAccess::convertToIPMIPrivilege(usrPriv) & privMask; // Update all channels priv, only if it is not equivalent to // getUsrMgmtSyncIndex() if (userData->user[usrIdx] .userPrivAccess[getUsrMgmtSyncIndex()] .privilege != priv) { updateRequired = true; for (size_t chIndex = 0; chIndex < ipmiMaxChannels; ++chIndex) { userData->user[usrIdx] .userPrivAccess[chIndex] .privilege = priv; } } if (userData->user[usrIdx].userEnabled != usrEnabled) { updateRequired = true; userData->user[usrIdx].userEnabled = usrEnabled; } } // We are done with this obj. lets delete from MAP managedObjs.erase(usrObj); } else { updateRequired = true; deleteUserIndex(usrIdx); } } } // Walk through remnaining managedObj users list // Add them to ipmi data base for (const auto& usrObj : managedObjs) { std::vector usrGrps; std::string usrPriv, userName; bool usrEnabled = false; std::string usrObjPath = std::string(usrObj.first); if (getUserNameFromPath(usrObj.first.str, userName) != 0) { lg2::error("Error in user object path"); continue; } getUserObjProperties(usrObj.second, usrGrps, usrPriv, usrEnabled); // Add 'ipmi' group users if (std::find(usrGrps.begin(), usrGrps.end(), ipmiGrpName) != usrGrps.end()) { updateRequired = true; // CREATE NEW USER if (true != addUserEntry(userName, usrPriv, usrEnabled)) { break; } } } if (updateRequired) { // All userData slots update done. Lets write the data writeUserData(); } return; } } // namespace ipmi