| /* |
| // 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 <security/pam_appl.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <boost/interprocess/sync/named_recursive_mutex.hpp> |
| #include <boost/interprocess/sync/scoped_lock.hpp> |
| #include <cerrno> |
| #include <fstream> |
| #include <ipmid/types.hpp> |
| #include <nlohmann/json.hpp> |
| #include <phosphor-logging/elog-errors.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <regex> |
| #include <sdbusplus/bus/match.hpp> |
| #include <sdbusplus/server/object.hpp> |
| #include <variant> |
| #include <xyz/openbmc_project/Common/error.hpp> |
| #include <xyz/openbmc_project/User/Common/error.hpp> |
| |
| 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* getSubTreeMethod = "GetSubTree"; |
| 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<std::string, (PRIVILEGE_OEM + 1)> 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<std::string, std::vector<std::string>>; |
| |
| using NoResource = |
| sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource; |
| |
| using InternalFailure = |
| sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; |
| |
| std::unique_ptr<sdbusplus::bus::match_t> userUpdatedSignal |
| __attribute__((init_priority(101))); |
| std::unique_ptr<sdbusplus::bus::match_t> userMgrRenamedSignal |
| __attribute__((init_priority(101))); |
| std::unique_ptr<sdbusplus::bus::match_t> 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<std::string>({intf})); |
| |
| auto mapperResponseMsg = bus.call(mapperCall); |
| |
| std::map<std::string, std::vector<std::string>> 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) |
| { |
| log<level::ERR>("Failed to set property", |
| entry("PROPERTY=%s", property.c_str()), |
| entry("PATH=%s", objPath.c_str()), |
| entry("INTERFACE=%s", interface.c_str())); |
| throw; |
| } |
| } |
| |
| static 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<char*>(userData->user[usrIndex].userName), 0, |
| ipmiMaxUserName); |
| if (userName == curName) |
| { |
| break; // found the entry |
| } |
| } |
| if (usrIndex > ipmiMaxUsers) |
| { |
| log<level::DEBUG>("User not found for signal", |
| entry("USER_NAME=%s", userName.c_str()), |
| entry("USER_EVENT=%d", userEvent)); |
| return; |
| } |
| switch (userEvent) |
| { |
| case UserUpdateEvent::userDeleted: |
| { |
| usrAccess.deleteUserIndex(usrIndex); |
| break; |
| } |
| case UserUpdateEvent::userPrivUpdated: |
| { |
| uint8_t userPriv = |
| static_cast<uint8_t>( |
| 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<uint8_t*>(userData->user[usrIndex].userName), |
| static_cast<uint8_t*>(userData->user[usrIndex].userName) + |
| sizeof(userData->user[usrIndex].userName), |
| 0); |
| std::strncpy( |
| reinterpret_cast<char*>(userData->user[usrIndex].userName), |
| newUserName.c_str(), ipmiMaxUserName); |
| ipmiRenameUserEntryPassword(userName, newUserName); |
| break; |
| } |
| case UserUpdateEvent::userStateUpdated: |
| { |
| userData->user[usrIndex].userEnabled = enabled; |
| break; |
| } |
| default: |
| { |
| log<level::ERR>("Unhandled user event", |
| entry("USER_EVENT=%d", userEvent)); |
| return; |
| } |
| } |
| } |
| usrAccess.writeUserData(); |
| log<level::DEBUG>("User event handled successfully", |
| entry("USER_NAME=%s", userName.c_str()), |
| entry("USER_EVENT=%d", 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<std::string> 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<std::string> 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 |
| { |
| log<level::ERR>("Unknown user update signal", |
| entry("SIGNAL=%s", signal.c_str())); |
| return; |
| } |
| |
| if (signal.empty() || userName.empty() || |
| (signal == userRenamedSignal && newUserName.empty())) |
| { |
| log<level::ERR>("Invalid inputs received"); |
| return; |
| } |
| |
| boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex> |
| 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<std::string>(prop.second); |
| userEvent = UserUpdateEvent::userPrivUpdated; |
| } |
| else if (member == userGrpProperty) |
| { |
| groups = std::get<std::vector<std::string>>(prop.second); |
| userEvent = UserUpdateEvent::userGrpUpdated; |
| } |
| else if (member == userEnabledProperty) |
| { |
| enabled = std::get<bool>(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) |
| { |
| log<level::DEBUG>( |
| "Failed to excute method", |
| entry("METHOD=%s", getAllPropertiesMethod), |
| entry("PATH=%s", 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()) |
| { |
| log<level::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::named_recursive_mutex>( |
| 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<uint8_t*>(userInfo), |
| reinterpret_cast<uint8_t*>(userInfo) + sizeof(*userInfo), |
| reinterpret_cast<uint8_t*>(&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<CommandPrivilege>(privNoAccess); |
| } |
| log<level::ERR>("Error in converting to IPMI privilege", |
| entry("PRIV=%s", value.c_str())); |
| throw std::out_of_range("Out of range - convertToIPMIPrivilege"); |
| } |
| else |
| { |
| return static_cast<CommandPrivilege>( |
| std::distance(ipmiPrivIndex.begin(), iter)); |
| } |
| } |
| |
| std::string UserAccess::convertToSystemPrivilege(const CommandPrivilege& value) |
| { |
| if (value == static_cast<CommandPrivilege>(privNoAccess)) |
| { |
| return ""; |
| } |
| try |
| { |
| return ipmiPrivIndex.at(value); |
| } |
| catch (const std::out_of_range& e) |
| { |
| log<level::ERR>("Error in converting to system privilege", |
| entry("PRIV=%d", static_cast<uint8_t>(value))); |
| throw std::out_of_range("Out of range - convertToSystemPrivilege"); |
| } |
| } |
| |
| bool UserAccess::isValidUserName(const std::string& userName) |
| { |
| if (userName.empty()) |
| { |
| log<level::ERR>("userName is empty"); |
| return false; |
| } |
| if (!std::regex_match(userName.c_str(), |
| std::regex("[a-zA-z_][a-zA-Z_0-9]*"))) |
| { |
| log<level::ERR>("Unsupported characters in user name"); |
| return false; |
| } |
| if (userName == "root") |
| { |
| log<level::ERR>("Invalid user name - root"); |
| return false; |
| } |
| std::map<DbusUserObjPath, DbusUserObjValue> 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) |
| { |
| log<level::ERR>("Failed to excute method", |
| entry("METHOD=%s", getSubTreeMethod), |
| entry("PATH=%s", userMgrObjBasePath)); |
| return false; |
| } |
| |
| sdbusplus::message::object_path tempUserPath(userObjBasePath); |
| tempUserPath /= userName; |
| std::string usersPath(tempUserPath); |
| |
| if (properties.find(usersPath) != properties.end()) |
| { |
| log<level::DEBUG>("User name already exists", |
| entry("USER_NAME=%s", userName.c_str())); |
| 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<char*>(appdataPtr); |
| size_t appPassSize = std::strlen(appPass); |
| |
| if (appPassSize >= PAM_MAX_RESP_SIZE) |
| { |
| return PAM_CONV_ERR; |
| } |
| |
| char* pass = reinterpret_cast<char*>(malloc(appPassSize + 1)); |
| if (pass == nullptr) |
| { |
| return PAM_BUF_ERR; |
| } |
| |
| void* ptr = |
| calloc(static_cast<size_t>(numMsg), sizeof(struct pam_response)); |
| if (ptr == nullptr) |
| { |
| free(pass); |
| return PAM_BUF_ERR; |
| } |
| |
| std::strncpy(pass, appPass, appPassSize + 1); |
| |
| *resp = reinterpret_cast<pam_response*>(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<char*>(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<char*>(password.data())}; |
| |
| pam_handle_t* localAuthHandle = NULL; // this gets set by pam_start |
| |
| if (pam_start("dropbear", username.data(), &localConversation, |
| &localAuthHandle) != PAM_SUCCESS) |
| { |
| log<level::ERR>("User Authentication Failure"); |
| return false; |
| } |
| |
| int retval = pam_authenticate(localAuthHandle, |
| PAM_SILENT | PAM_DISALLOW_NULL_AUTHTOK); |
| |
| if (retval != PAM_SUCCESS) |
| { |
| log<level::DEBUG>("pam_authenticate returned failure", |
| entry("ERROR=%d", 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) |
| { |
| log<level::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) |
| { |
| log<level::DEBUG>("User Name not found", |
| entry("USER-ID=%d", (uint8_t)userId)); |
| return ccParmOutOfRange; |
| } |
| |
| ipmi::SecureString passwd; |
| passwd.assign(reinterpret_cast<const char*>(userPassword), 0, |
| maxIpmi20PasswordSize); |
| int retval = pamUpdatePasswd(userName.c_str(), passwd.c_str()); |
| |
| switch (retval) |
| { |
| case PAM_SUCCESS: |
| { |
| return ccSuccess; |
| } |
| case PAM_AUTHTOK_ERR: |
| { |
| log<level::DEBUG>("Bad authentication token"); |
| return ccInvalidFieldRequest; |
| } |
| default: |
| { |
| log<level::DEBUG>("Failed to update password", |
| entry("USER-ID=%d", (uint8_t)userId)); |
| return ccUnspecifiedError; |
| } |
| } |
| } |
| |
| Cc UserAccess::setUserEnabledState(const uint8_t userId, |
| const bool& enabledState) |
| { |
| if (!isValidUserId(userId)) |
| { |
| return ccParmOutOfRange; |
| } |
| boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex> |
| userLock{*userMutex}; |
| UserInfo* userInfo = getUserInfo(userId); |
| std::string userName; |
| userName.assign(reinterpret_cast<char*>(userInfo->userName), 0, |
| ipmiMaxUserName); |
| if (userName.empty()) |
| { |
| log<level::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) |
| { |
| log<level::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<boost::interprocess::named_recursive_mutex> |
| 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) |
| { |
| log<level::ERR>("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<boost::interprocess::named_recursive_mutex> |
| userLock{*userMutex}; |
| UserInfo* userInfo = getUserInfo(userId); |
| std::string userName; |
| userName.assign(reinterpret_cast<char*>(userInfo->userName), 0, |
| ipmiMaxUserName); |
| if (userName.empty()) |
| { |
| log<level::DEBUG>("User name not set / invalid"); |
| return ccUnspecifiedError; |
| } |
| std::string priv = convertToSystemPrivilege( |
| static_cast<CommandPrivilege>(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) |
| { |
| log<level::DEBUG>("Write user data failed"); |
| return ccUnspecifiedError; |
| } |
| return ccSuccess; |
| } |
| |
| uint8_t UserAccess::getUserId(const std::string& userName) |
| { |
| boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex> |
| userLock{*userMutex}; |
| checkAndReloadUserData(); |
| // user index 0 is reserved, starts with 1 |
| size_t usrIndex = 1; |
| for (; usrIndex <= ipmiMaxUsers; ++usrIndex) |
| { |
| std::string curName( |
| reinterpret_cast<char*>(usersTbl.user[usrIndex].userName), 0, |
| ipmiMaxUserName); |
| if (userName == curName) |
| { |
| break; // found the entry |
| } |
| } |
| if (usrIndex > ipmiMaxUsers) |
| { |
| log<level::DEBUG>("User not found", |
| entry("USER_NAME=%s", userName.c_str())); |
| 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<char*>(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<boost::interprocess::named_recursive_mutex> |
| 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) |
| { |
| log<level::DEBUG>("Failed to excute method", |
| entry("METHOD=%s", deleteUserMethod), |
| entry("PATH=%s", userPath.c_str())); |
| 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) |
| { |
| log<level::DEBUG>("Failed to excute method", |
| entry("METHOD=%s", createUserMethod), |
| entry("PATH=%s", userMgrObjBasePath)); |
| return ccUnspecifiedError; |
| } |
| |
| std::memset(userInfo->userName, 0, sizeof(userInfo->userName)); |
| std::memcpy(userInfo->userName, |
| static_cast<const void*>(userName.data()), userName.size()); |
| userInfo->userInSystem = true; |
| for (size_t chIndex = 0; chIndex < ipmiMaxChannels; chIndex++) |
| { |
| userInfo->userPrivAccess[chIndex].privilege = |
| static_cast<uint8_t>(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) |
| { |
| log<level::DEBUG>("Failed to excute method", |
| entry("METHOD=%s", renameUserMethod), |
| entry("PATH=%s", userMgrObjBasePath)); |
| return ccUnspecifiedError; |
| } |
| std::fill(static_cast<uint8_t*>(userInfo->userName), |
| static_cast<uint8_t*>(userInfo->userName) + |
| sizeof(userInfo->userName), |
| 0); |
| |
| std::memset(userInfo->userName, 0, sizeof(userInfo->userName)); |
| std::memcpy(userInfo->userName, |
| static_cast<const void*>(userName.data()), userName.size()); |
| |
| ipmiRenameUserEntryPassword(oldUser, userName); |
| userInfo->userInSystem = true; |
| } |
| else if (!validUser) |
| { |
| return ccInvalidFieldRequest; |
| } |
| try |
| { |
| writeUserData(); |
| } |
| catch (const std::exception& e) |
| { |
| log<level::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,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "OEM_payload1":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "OEM_payload2":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "OEM_payload3":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "OEM_payload4":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "OEM_payload5":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "OEM_payload6":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "OEM_payload7":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "std_payload0":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "std_payload1":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "std_payload2":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "std_payload3":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "std_payload4":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "std_payload5":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "std_payload6":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * "std_payload7":[false,...<repeat 'ipmiMaxChannels - 1' times>], |
| * } |
| */ |
| static const Json constructJsonPayloadEnables( |
| const std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte>& |
| stdPayload, |
| const std::array<std::array<bool, ipmiMaxChannels>, 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<std::array<bool, ipmiMaxChannels>, payloadsPerByte>& stdPayload, |
| std::array<std::array<bool, ipmiMaxChannels>, 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<std::array<bool, ipmiMaxChannels>, payloadsPerByte>& |
| stdPayload, |
| const std::array<std::array<bool, ipmiMaxChannels>, 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<uint8_t>(ipmi::PayloadType::SOL)] = |
| stdPayload[static_cast<uint8_t>(ipmi::PayloadType::SOL)][chIndex]; |
| } |
| } |
| |
| void UserAccess::readUserData() |
| { |
| boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex> |
| userLock{*userMutex}; |
| |
| std::ifstream iUsrData(ipmiUserDataFile, std::ios::in | std::ios::binary); |
| if (!iUsrData.good()) |
| { |
| log<level::ERR>("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) |
| { |
| log<level::ERR>( |
| "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()) |
| { |
| log<level::ERR>("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::string>(); |
| std::strncpy(reinterpret_cast<char*>(usersTbl.user[usrIndex].userName), |
| userName.c_str(), ipmiMaxUserName); |
| |
| std::vector<std::string> privilege = |
| userInfo[jsonPriv].get<std::vector<std::string>>(); |
| std::vector<bool> ipmiEnabled = |
| userInfo[jsonIpmiEnabled].get<std::vector<bool>>(); |
| std::vector<bool> linkAuthEnabled = |
| userInfo[jsonLinkAuthEnabled].get<std::vector<bool>>(); |
| std::vector<bool> accessCallback = |
| userInfo[jsonAccCallbk].get<std::vector<bool>>(); |
| |
| // Payload Enables Processing. |
| std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte> |
| stdPayload = {}; |
| std::array<std::array<bool, ipmiMaxChannels>, 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<std::array<bool, ipmiMaxChannels>>(); |
| oemPayload[payloadNum] = |
| jsonPayloadEnabled[oemPayloadStream.str()] |
| .get<std::array<bool, ipmiMaxChannels>>(); |
| |
| if (stdPayload[payloadNum].size() != ipmiMaxChannels || |
| oemPayload[payloadNum].size() != ipmiMaxChannels) |
| { |
| log<level::ERR>("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<uint8_t>(ipmi::PayloadType::SOL)].fill(true); |
| } |
| |
| if (privilege.size() != ipmiMaxChannels || |
| ipmiEnabled.size() != ipmiMaxChannels || |
| linkAuthEnabled.size() != ipmiMaxChannels || |
| accessCallback.size() != ipmiMaxChannels) |
| { |
| log<level::ERR>("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<uint8_t>( |
| 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<bool>(); |
| usersTbl.user[usrIndex].userInSystem = |
| userInfo[jsonUserInSys].get<bool>(); |
| usersTbl.user[usrIndex].fixedUserName = |
| userInfo[jsonFixedUser].get<bool>(); |
| } |
| |
| log<level::DEBUG>("User data read from IPMI data file"); |
| iUsrData.close(); |
| // Update the timestamp |
| fileLastUpdatedTime = getUpdatedFileTime(); |
| return; |
| } |
| |
| void UserAccess::writeUserData() |
| { |
| boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex> |
| 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<char*>(usersTbl.user[usrIndex].userName), 0, |
| ipmiMaxUserName); |
| std::vector<std::string> privilege(ipmiMaxChannels); |
| std::vector<bool> ipmiEnabled(ipmiMaxChannels); |
| std::vector<bool> linkAuthEnabled(ipmiMaxChannels); |
| std::vector<bool> accessCallback(ipmiMaxChannels); |
| |
| std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte> |
| stdPayload; |
| std::array<std::array<bool, ipmiMaxChannels>, payloadsPerByte> |
| oemPayload; |
| |
| for (size_t chIndex = 0; chIndex < ipmiMaxChannels; chIndex++) |
| { |
| privilege[chIndex] = |
| convertToSystemPrivilege(static_cast<CommandPrivilege>( |
| 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) |
| { |
| log<level::ERR>("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<ssize_t>(writeStr.size())) |
| { |
| close(fd); |
| log<level::ERR>("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) |
| { |
| log<level::ERR>("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<char*>(userData->user[usrIndex].userName), 0, |
| ipmiMaxUserName); |
| if (userName == curName) |
| { |
| log<level::DEBUG>("User name exists", |
| entry("USER_NAME=%s", userName.c_str())); |
| return false; // user name exists. |
| } |
| |
| if ((!userData->user[usrIndex].userInSystem) && |
| (userData->user[usrIndex].userName[0] == '\0') && |
| (freeIndex == 0xFF)) |
| { |
| freeIndex = usrIndex; |
| } |
| } |
| if (freeIndex == 0xFF) |
| { |
| log<level::ERR>("No empty slots found"); |
| return false; |
| } |
| std::strncpy(reinterpret_cast<char*>(userData->user[freeIndex].userName), |
| userName.c_str(), ipmiMaxUserName); |
| uint8_t priv = |
| static_cast<uint8_t>(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<char*>(userData->user[usrIdx].userName), 0, |
| ipmiMaxUserName); |
| ipmiClearUserEntryPassword(userName); |
| std::fill(static_cast<uint8_t*>(userData->user[usrIdx].userName), |
| static_cast<uint8_t*>(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<uint8_t*>(&usersTbl), |
| reinterpret_cast<uint8_t*>(&usersTbl) + sizeof(usersTbl), 0); |
| readUserData(); |
| } |
| return; |
| } |
| |
| UsersTbl* UserAccess::getUsersTblPtr() |
| { |
| // reload data before using it. |
| checkAndReloadUserData(); |
| return &usersTbl; |
| } |
| |
| void UserAccess::getSystemPrivAndGroups() |
| { |
| std::map<std::string, PrivAndGroupType> 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) |
| { |
| log<level::DEBUG>("Failed to excute method", |
| entry("METHOD=%s", getAllPropertiesMethod), |
| entry("PATH=%s", userMgrObjBasePath)); |
| return; |
| } |
| for (const auto& t : properties) |
| { |
| auto key = t.first; |
| if (key == allPrivProperty) |
| { |
| availablePrivileges = std::get<std::vector<std::string>>(t.second); |
| } |
| else if (key == allGrpProperty) |
| { |
| availableGroups = std::get<std::vector<std::string>>(t.second); |
| } |
| } |
| // TODO: Implement Supported Privilege & Groups verification logic |
| return; |
| } |
| |
| std::timespec UserAccess::getUpdatedFileTime() |
| { |
| struct stat fileStat; |
| if (stat(ipmiUserDataFile, &fileStat) != 0) |
| { |
| log<level::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<std::string>& usrGrps, |
| std::string& usrPriv, bool& usrEnabled) |
| { |
| for (const auto& t : properties) |
| { |
| std::string key = t.first; |
| if (key == userPrivProperty) |
| { |
| usrPriv = std::get<std::string>(t.second); |
| } |
| else if (key == userGrpProperty) |
| { |
| usrGrps = std::get<std::vector<std::string>>(t.second); |
| } |
| else if (key == userEnabledProperty) |
| { |
| usrEnabled = std::get<bool>(t.second); |
| } |
| } |
| return; |
| } |
| |
| int UserAccess::getUserObjProperties(const DbusUserObjValue& userObjs, |
| std::vector<std::string>& 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<boost::interprocess::named_recursive_mutex> |
| userLock{*userMutex}; |
| try |
| { |
| readUserData(); |
| } |
| catch (const std::ios_base::failure& e) |
| { // File is empty, create it for the first time |
| std::fill(reinterpret_cast<uint8_t*>(&usersTbl), |
| reinterpret_cast<uint8_t*>(&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<uint8_t>( |
| 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) |
| { |
| log<level::ERR>("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()) |
| { |
| log<level::DEBUG>("Registering signal handler"); |
| userUpdatedSignal = std::make_unique<sdbusplus::bus::match_t>( |
| 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<sdbusplus::bus::match_t>( |
| 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<sdbusplus::bus::match_t>( |
| 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<DbusUserObjPath, DbusUserObjValue> 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) |
| { |
| log<level::DEBUG>("Failed to excute method", |
| entry("METHOD=%s", getSubTreeMethod), |
| entry("PATH=%s", 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<std::string> usrGrps; |
| std::string usrPriv; |
| |
| std::string userName( |
| reinterpret_cast<char*>(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<std::string> usrGrps; |
| std::string usrPriv, userName; |
| bool usrEnabled = false; |
| std::string usrObjPath = std::string(usrObj.first); |
| if (getUserNameFromPath(usrObj.first.str, userName) != 0) |
| { |
| log<level::ERR>("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 |