diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | apphandler.cpp | 3 | ||||
-rw-r--r-- | user_channel/user_layer.cpp | 112 | ||||
-rw-r--r-- | user_channel/user_layer.hpp | 143 | ||||
-rw-r--r-- | user_channel/user_mgmt.cpp | 1304 | ||||
-rw-r--r-- | user_channel/user_mgmt.hpp | 295 | ||||
-rw-r--r-- | user_channel/usercommands.cpp | 473 | ||||
-rw-r--r-- | user_channel/usercommands.hpp | 36 |
8 files changed, 2361 insertions, 7 deletions
diff --git a/Makefile.am b/Makefile.am index 7be3bc9..5e03648 100644 --- a/Makefile.am +++ b/Makefile.am @@ -66,6 +66,7 @@ libuserlayerdir = ${libdir} libuserlayer_LTLIBRARIES = libuserlayer.la libuserlayer_la_SOURCES = \ user_channel/user_layer.cpp \ + user_channel/user_mgmt.cpp \ user_channel/passwd_mgr.cpp libuserlayer_la_LDFLAGS = $(SYSTEMD_LIBS) $(libmapper_LIBS) \ @@ -97,6 +98,7 @@ libipmi20_la_SOURCES = \ ipmi_fru_info_area.cpp \ read_fru_data.cpp \ sensordatahandler.cpp \ + user_channel/usercommands.cpp \ $(libipmi20_BUILT_LIST) @CODE_COVERAGE_RULES@ diff --git a/apphandler.cpp b/apphandler.cpp index fd58218..ac06f50 100644 --- a/apphandler.cpp +++ b/apphandler.cpp @@ -6,6 +6,7 @@ #include "sys_info_param.hpp" #include "transporthandler.hpp" #include "types.hpp" +#include "user_channel/usercommands.hpp" #include "utils.hpp" #include <arpa/inet.h> @@ -874,7 +875,6 @@ void register_netfn_app_functions() // <Get Channel Cipher Suites Command> ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_CHAN_CIPHER_SUITES, NULL, getChannelCipherSuites, PRIVILEGE_CALLBACK); - // <Set Channel Access Command> ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_CHAN_ACCESS, NULL, ipmi_set_channel_access, PRIVILEGE_ADMIN); @@ -882,5 +882,6 @@ void register_netfn_app_functions() // <Get System Info Command> ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_SYSTEM_INFO, NULL, ipmi_app_get_system_info, PRIVILEGE_USER); + ipmi::registerUserIpmiFunctions(); return; } diff --git a/user_channel/user_layer.cpp b/user_channel/user_layer.cpp index 06cdd68..3e4490c 100644 --- a/user_channel/user_layer.cpp +++ b/user_channel/user_layer.cpp @@ -17,6 +17,8 @@ #include "user_layer.hpp" #include "passwd_mgr.hpp" +#include "user_mgmt.hpp" + namespace { ipmi::PasswdMgr passwdMgr; @@ -24,6 +26,13 @@ ipmi::PasswdMgr passwdMgr; namespace ipmi { + +ipmi_ret_t ipmiUserInit() +{ + getUserAccessObject(); + return IPMI_CC_OK; +} + std::string ipmiUserGetPassword(const std::string& userName) { return passwdMgr.getPasswdByUserName(userName); @@ -48,4 +57,107 @@ ipmi_ret_t ipmiRenameUserEntryPassword(const std::string& userName, return IPMI_CC_OK; } +bool ipmiUserIsValidUserId(const uint8_t& userId) +{ + return UserAccess::isValidUserId(userId); +} + +bool ipmiUserIsValidChannel(const uint8_t& chNum) +{ + return UserAccess::isValidChannel(chNum); +} + +bool ipmiUserIsValidPrivilege(const uint8_t& priv) +{ + return UserAccess::isValidPrivilege(priv); +} + +uint8_t ipmiUserGetUserId(const std::string& userName) +{ + return getUserAccessObject().getUserId(userName); +} + +ipmi_ret_t ipmiUserSetUserName(const uint8_t& userId, const char* userName) +{ + return getUserAccessObject().setUserName(userId, userName); +} + +ipmi_ret_t ipmiUserGetUserName(const uint8_t& userId, std::string& userName) +{ + return getUserAccessObject().getUserName(userId, userName); +} + +ipmi_ret_t ipmiUserGetAllCounts(uint8_t& maxChUsers, uint8_t& enabledUsers, + uint8_t& fixedUsers) +{ + maxChUsers = ipmiMaxUsers; + UsersTbl* userData = getUserAccessObject().getUsersTblPtr(); + enabledUsers = 0; + fixedUsers = 0; + // user index 0 is reserved, starts with 1 + for (size_t count = 1; count <= ipmiMaxUsers; ++count) + { + if (userData->user[count].userEnabled) + { + enabledUsers++; + } + if (userData->user[count].fixedUserName) + { + fixedUsers++; + } + } + return IPMI_CC_OK; +} + +ipmi_ret_t ipmiUserCheckEnabled(const uint8_t& userId, bool& state) +{ + if (!UserAccess::isValidUserId(userId)) + { + return IPMI_CC_PARM_OUT_OF_RANGE; + } + UserInfo* userInfo = getUserAccessObject().getUserInfo(userId); + state = userInfo->userEnabled; + return IPMI_CC_OK; +} + +ipmi_ret_t ipmiUserGetPrivilegeAccess(const uint8_t& userId, + const uint8_t& chNum, + PrivAccess& privAccess) +{ + + if (!UserAccess::isValidChannel(chNum)) + { + return IPMI_CC_INVALID_FIELD_REQUEST; + } + if (!UserAccess::isValidUserId(userId)) + { + return IPMI_CC_PARM_OUT_OF_RANGE; + } + UserInfo* userInfo = getUserAccessObject().getUserInfo(userId); + privAccess.privilege = userInfo->userPrivAccess[chNum].privilege; + privAccess.ipmiEnabled = userInfo->userPrivAccess[chNum].ipmiEnabled; + privAccess.linkAuthEnabled = + userInfo->userPrivAccess[chNum].linkAuthEnabled; + privAccess.accessCallback = userInfo->userPrivAccess[chNum].accessCallback; + + return IPMI_CC_OK; +} + +ipmi_ret_t ipmiUserSetPrivilegeAccess(const uint8_t& userId, + const uint8_t& chNum, + const PrivAccess& privAccess, + const bool& otherPrivUpdates) +{ + UserPrivAccess userPrivAccess; + userPrivAccess.privilege = privAccess.privilege; + if (otherPrivUpdates) + { + userPrivAccess.ipmiEnabled = privAccess.ipmiEnabled; + userPrivAccess.linkAuthEnabled = privAccess.linkAuthEnabled; + userPrivAccess.accessCallback = privAccess.accessCallback; + } + return getUserAccessObject().setUserPrivilegeAccess( + userId, chNum, userPrivAccess, otherPrivUpdates); +} + } // namespace ipmi diff --git a/user_channel/user_layer.hpp b/user_channel/user_layer.hpp index a946e4c..5136e86 100644 --- a/user_channel/user_layer.hpp +++ b/user_channel/user_layer.hpp @@ -20,9 +20,46 @@ namespace ipmi { + +// TODO: Has to be replaced with proper channel number assignment logic +enum class EChannelID : uint8_t +{ + chanLan1 = 0x01 +}; + +static constexpr uint8_t invalidUserId = 0xFF; +static constexpr uint8_t reservedUserId = 0x0; +static constexpr uint8_t ipmiMaxUserName = 16; +static constexpr uint8_t ipmiMaxUsers = 15; +static constexpr uint8_t ipmiMaxChannels = 16; + +struct PrivAccess +{ +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t privilege : 4; + uint8_t ipmiEnabled : 1; + uint8_t linkAuthEnabled : 1; + uint8_t accessCallback : 1; + uint8_t reserved : 1; +#endif +#if BYTE_ORDER == BIG_ENDIAN + uint8_t reserved : 1; + uint8_t accessCallback : 1; + uint8_t linkAuthEnabled : 1; + uint8_t ipmiEnabled : 1; + uint8_t privilege : 4; +#endif +} __attribute__((packed)); + +/** @brief initializes user management + * + * @return IPMI_CC_OK for success, others for failure. + */ +ipmi_ret_t ipmiUserInit(); + /** @brief The ipmi get user password layer call * - * @param[in] userName + * @param[in] userName - user name * * @return password or empty string */ @@ -31,7 +68,7 @@ std::string ipmiUserGetPassword(const std::string& userName); /** @brief The IPMI call to clear password entry associated with specified * username * - * @param[in] userName + * @param[in] userName - user name to be removed * * @return 0 on success, non-zero otherwise. */ @@ -40,14 +77,108 @@ ipmi_ret_t ipmiClearUserEntryPassword(const std::string& userName); /** @brief The IPMI call to reuse password entry for the renamed user * to another one * - * @param[in] userName - * @param[in] newUserName + * @param[in] userName - user name which has to be renamed + * @param[in] newUserName - new user name * * @return 0 on success, non-zero otherwise. */ ipmi_ret_t ipmiRenameUserEntryPassword(const std::string& userName, const std::string& newUserName); -// TODO: Define required user layer API Call's which user layer shared library -// must implement. +/** @brief determines valid userId + * + * @param[in] userId - user id + * + * @return true if valid, false otherwise + */ +bool ipmiUserIsValidUserId(const uint8_t& userId); + +/** @brief determines valid channel + * + * @param[in] chNum- channel number + * + * @return true if valid, false otherwise + */ +bool ipmiUserIsValidChannel(const uint8_t& chNum); + +/** @brief determines valid privilege level + * + * @param[in] priv - privilege level + * + * @return true if valid, false otherwise + */ +bool ipmiUserIsValidPrivilege(const uint8_t& priv); + +/** @brief get user id corresponding to the user name + * + * @param[in] userName - user name + * + * @return userid. Will return 0xff if no user id found + */ +uint8_t ipmiUserGetUserId(const std::string& userName); + +/** @brief set's user name + * + * @param[in] userId - user id + * @param[in] userName - user name + * + * @return IPMI_CC_OK for success, others for failure. + */ +ipmi_ret_t ipmiUserSetUserName(const uint8_t& userId, const char* userName); + +/** @brief get user name + * + * @param[in] userId - user id + * @param[out] userName - user name + * + * @return IPMI_CC_OK for success, others for failure. + */ +ipmi_ret_t ipmiUserGetUserName(const uint8_t& userId, std::string& userName); + +/** @brief provides available fixed, max, and enabled user counts + * + * @param[out] maxChUsers - max channel users + * @param[out] enabledUsers - enabled user count + * @param[out] fixedUsers - fixed user count + * + * @return IPMI_CC_OK for success, others for failure. + */ +ipmi_ret_t ipmiUserGetAllCounts(uint8_t& maxChUsers, uint8_t& enabledUsers, + uint8_t& fixedUsers); + +/** @brief determines whether user is enabled + * + * @param[in] userId - user id + *..@param[out] state - state of the user + * + * @return IPMI_CC_OK for success, others for failure. + */ +ipmi_ret_t ipmiUserCheckEnabled(const uint8_t& userId, bool& state); + +/** @brief provides user privilege access data + * + * @param[in] userId - user id + * @param[in] chNum - channel number + * @param[out] privAccess - privilege access data + * + * @return IPMI_CC_OK for success, others for failure. + */ +ipmi_ret_t ipmiUserGetPrivilegeAccess(const uint8_t& userId, + const uint8_t& chNum, + PrivAccess& privAccess); + +/** @brief sets user privilege access data + * + * @param[in] userId - user id + * @param[in] chNum - channel number + * @param[in] privAccess - privilege access data + * @param[in] otherPrivUpdate - flags to indicate other fields update + * + * @return IPMI_CC_OK for success, others for failure. + */ +ipmi_ret_t ipmiUserSetPrivilegeAccess(const uint8_t& userId, + const uint8_t& chNum, + const PrivAccess& privAccess, + const bool& otherPrivUpdate); + } // namespace ipmi diff --git a/user_channel/user_mgmt.cpp b/user_channel/user_mgmt.cpp new file mode 100644 index 0000000..e320e3f --- /dev/null +++ b/user_channel/user_mgmt.cpp @@ -0,0 +1,1304 @@ +/* +// 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 <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 <host-ipmid/ipmid-host-cmd.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 <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* 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 = + sdbusplus::message::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(nullptr); +std::unique_ptr<sdbusplus::bus::match_t> userMgrRenamedSignal(nullptr); +std::unique_ptr<sdbusplus::bus::match_t> userPropertiesSignal(nullptr); + +// TODO: Below code can be removed once it is moved to common layer libmiscutil +std::string getUserService(sdbusplus::bus::bus& 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::bus& 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::SdBusError& 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::bus bus(ipmid_get_sd_bus_connection()); + static std::string userMgmtService; + if (userMgmtService.empty()) + { + try + { + userMgmtService = + ipmi::getUserService(bus, userMgrInterface, userMgrObjBasePath); + } + catch (const sdbusplus::exception::SdBusError& e) + { + userMgmtService.clear(); + } + } + return userMgmtService; +} + +UserAccess& getUserAccessObject() +{ + static UserAccess userAccess; + return userAccess; +} + +int getUserNameFromPath(const std::string& path, std::string& userName) +{ + static size_t pos = strlen(userObjBasePath) + 1; + if (path.find(userObjBasePath) == std::string::npos) + { + return -EINVAL; + } + userName.assign(path, pos, path.size()); + 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::message& msg) +{ + static sdbusplus::bus::bus bus(ipmid_get_sd_bus_connection()); + std::string signal = msg.get_member(); + std::string userName, update, 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 = prop.second.get<std::string>(); + userEvent = UserUpdateEvent::userPrivUpdated; + } + else if (member == userGrpProperty) + { + groups = prop.second.get<std::vector<std::string>>(); + userEvent = UserUpdateEvent::userGrpUpdated; + } + else if (member == userEnabledProperty) + { + enabled = prop.second.get<bool>(); + 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::SdBusError& 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); + + initUserDataFile(); + getSystemPrivAndGroups(); + sigHndlrLock = boost::interprocess::file_lock(ipmiUserDataFile); + // Register it for single object and single process either netipimd / + // 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::message& 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::message& 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::message& msg) { + userUpdatedSignalHandler(*this, msg); + }); + signalHndlrObject = true; + } +} + +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) +{ + return ((priv >= PRIVILEGE_CALLBACK && priv <= PRIVILEGE_OEM) || + priv == privNoAccess); +} + +uint8_t UserAccess::getUsrMgmtSyncIndex() +{ + // TODO: Need to get LAN1 channel number dynamically, + // which has to be in sync with system user privilege + // level(Phosphor-user-manager). Note: For time being chanLan1 is marked as + // sync index to the user-manager privilege.. + return static_cast<uint8_t>(EChannelID::chanLan1); +} + +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 char* userNameInChar) +{ + if (!userNameInChar) + { + log<level::ERR>("null ptr"); + return false; + } + std::string userName(userNameInChar, 0, ipmiMaxUserName); + 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::SdBusError& e) + { + log<level::ERR>("Failed to excute method", + entry("METHOD=%s", getSubTreeMethod), + entry("PATH=%s", userMgrObjBasePath)); + return false; + } + + std::string usersPath = std::string(userObjBasePath) + "/" + userName; + if (properties.find(usersPath) != properties.end()) + { + log<level::DEBUG>("User name already exists", + entry("USER_NAME=%s", userName.c_str())); + return false; + } + + return true; +} + +ipmi_ret_t UserAccess::setUserPrivilegeAccess(const uint8_t& userId, + const uint8_t& chNum, + const UserPrivAccess& privAccess, + const bool& otherPrivUpdates) +{ + if (!isValidChannel(chNum)) + { + return IPMI_CC_INVALID_FIELD_REQUEST; + } + if (!isValidUserId(userId)) + { + return IPMI_CC_PARM_OUT_OF_RANGE; + } + 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 IPMI_CC_UNSPECIFIED_ERROR; + } + std::string priv = convertToSystemPrivilege( + static_cast<CommandPrivilege>(privAccess.privilege)); + if (priv.empty()) + { + return IPMI_CC_PARM_OUT_OF_RANGE; + } + uint8_t syncIndex = getUsrMgmtSyncIndex(); + if (chNum == syncIndex && + privAccess.privilege != userInfo->userPrivAccess[syncIndex].privilege) + { + std::string userPath = std::string(userObjBasePath) + "/" + userName; + setDbusProperty(bus, getUserServiceName().c_str(), userPath.c_str(), + 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 IPMI_CC_UNSPECIFIED_ERROR; + } + return IPMI_CC_OK; +} + +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; +} + +ipmi_ret_t UserAccess::getUserName(const uint8_t& userId, std::string& userName) +{ + if (!isValidUserId(userId)) + { + return IPMI_CC_PARM_OUT_OF_RANGE; + } + UserInfo* userInfo = getUserInfo(userId); + userName.assign(reinterpret_cast<char*>(userInfo->userName), 0, + ipmiMaxUserName); + return IPMI_CC_OK; +} + +ipmi_ret_t UserAccess::setUserName(const uint8_t& userId, + const char* userNameInChar) +{ + if (!isValidUserId(userId)) + { + return IPMI_CC_PARM_OUT_OF_RANGE; + } + + boost::interprocess::scoped_lock<boost::interprocess::named_recursive_mutex> + userLock{*userMutex}; + bool validUser = isValidUserName(userNameInChar); + std::string oldUser; + getUserName(userId, oldUser); + UserInfo* userInfo = getUserInfo(userId); + + std::string newUser(userNameInChar, 0, ipmiMaxUserName); + if (newUser.empty() && !oldUser.empty()) + { + // Delete existing user + std::string userPath = std::string(userObjBasePath) + "/" + oldUser; + try + { + auto method = bus.new_method_call( + getUserServiceName().c_str(), userPath.c_str(), + deleteUserInterface, deleteUserMethod); + auto reply = bus.call(method); + } + catch (const sdbusplus::exception::SdBusError& e) + { + log<level::DEBUG>("Failed to excute method", + entry("METHOD=%s", deleteUserMethod), + entry("PATH=%s", userPath.c_str())); + return IPMI_CC_UNSPECIFIED_ERROR; + } + std::fill(userInfo->userName, + userInfo->userName + sizeof(userInfo->userName), 0); + ipmiClearUserEntryPassword(oldUser); + userInfo->userInSystem = false; + } + else if (oldUser.empty() && !newUser.empty() && validUser) + { + try + { + // Create new user + auto method = bus.new_method_call( + getUserServiceName().c_str(), userMgrObjBasePath, + userMgrInterface, createUserMethod); + // TODO: Fetch proper privilege & enable state once set User access + // is implemented if LAN Channel specified, then create user for all + // groups follow channel privilege for user creation. + method.append(newUser.c_str(), availableGroups, "priv-admin", true); + auto reply = bus.call(method); + } + catch (const sdbusplus::exception::SdBusError& e) + { + log<level::DEBUG>("Failed to excute method", + entry("METHOD=%s", createUserMethod), + entry("PATH=%s", userMgrObjBasePath)); + return IPMI_CC_UNSPECIFIED_ERROR; + } + std::strncpy(reinterpret_cast<char*>(userInfo->userName), + userNameInChar, ipmiMaxUserName); + userInfo->userInSystem = true; + } + else if (oldUser != newUser && validUser) + { + try + { + // User rename + auto method = bus.new_method_call( + getUserServiceName().c_str(), userMgrObjBasePath, + userMgrInterface, renameUserMethod); + method.append(oldUser.c_str(), newUser.c_str()); + auto reply = bus.call(method); + } + catch (const sdbusplus::exception::SdBusError& e) + { + log<level::DEBUG>("Failed to excute method", + entry("METHOD=%s", renameUserMethod), + entry("PATH=%s", userMgrObjBasePath)); + return IPMI_CC_UNSPECIFIED_ERROR; + } + std::fill(static_cast<uint8_t*>(userInfo->userName), + static_cast<uint8_t*>(userInfo->userName) + + sizeof(userInfo->userName), + 0); + std::strncpy(reinterpret_cast<char*>(userInfo->userName), + userNameInChar, ipmiMaxUserName); + ipmiRenameUserEntryPassword(oldUser, newUser); + userInfo->userInSystem = true; + } + else if (!validUser) + { + return IPMI_CC_INVALID_FIELD_REQUEST; + } + try + { + writeUserData(); + } + catch (const std::exception& e) + { + log<level::DEBUG>("Write user data failed"); + return IPMI_CC_UNSPECIFIED_ERROR; + } + return IPMI_CC_OK; +} + +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"; + +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>>(); + 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]; + } + 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}; + + static std::string tmpFile{std::string(ipmiUserDataFile) + "_tmp"}; + std::ofstream oUsrData(tmpFile, std::ios::out | std::ios::binary); + if (!oUsrData.good()) + { + log<level::ERR>("Error in creating temporary IPMI user data file"); + throw std::ios_base::failure( + "Error in creating temporary IPMI user data file"); + } + + 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); + 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; + jsonUsersTbl.push_back(jsonUserInfo); + } + + oUsrData << jsonUsersTbl; + oUsrData.flush(); + oUsrData.close(); + + 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::time_t updateTime = getUpdatedFileTime(); + if (updateTime != fileLastUpdatedTime || updateTime == -EIO) + { + 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::SdBusError& 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 = t.second.get<std::vector<std::string>>(); + } + else if (key == allGrpProperty) + { + availableGroups = t.second.get<std::vector<std::string>>(); + } + } + // TODO: Implement Supported Privilege & Groups verification logic + return; +} + +std::time_t UserAccess::getUpdatedFileTime() +{ + struct stat fileStat; + if (stat(ipmiUserDataFile, &fileStat) != 0) + { + log<level::DEBUG>("Error in getting last updated time stamp"); + return -EIO; + } + return fileStat.st_mtime; +} + +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 = t.second.get<std::string>(); + } + else if (key == userGrpProperty) + { + usrGrps = t.second.get<std::vector<std::string>>(); + } + else if (key == userEnabledProperty) + { + usrEnabled = t.second.get<bool>(); + } + } + 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::initUserDataFile() +{ + 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; + } + } + writeUserData(); + } + 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::SdBusError& e) + { + log<level::DEBUG>("Failed to excute method", + entry("METHOD=%s", getSubTreeMethod), + entry("PATH=%s", userMgrObjBasePath)); + return; + } + + 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; + bool usrEnabled; + + std::string userName( + reinterpret_cast<char*>(userData->user[usrIdx].userName), 0, + ipmiMaxUserName); + std::string usersPath = + std::string(userObjBasePath) + "/" + userName; + + auto usrObj = managedObjs.find(usersPath); + if (usrObj != managedObjs.end()) + { + // User exist. Lets check and update other fileds + getUserObjProperties(usrObj->second, usrGrps, usrPriv, + usrEnabled); + if (std::find(usrGrps.begin(), usrGrps.end(), ipmiGrpName) == + usrGrps.end()) + { + // 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) + { + for (size_t chIndex = 0; chIndex < ipmiMaxChannels; + ++chIndex) + { + userData->user[usrIdx] + .userPrivAccess[chIndex] + .privilege = priv; + } + } + if (userData->user[usrIdx].userEnabled != usrEnabled) + { + userData->user[usrIdx].userEnabled = usrEnabled; + } + } + + // We are done with this obj. lets delete from MAP + managedObjs.erase(usrObj); + } + else + { + 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; + 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()) + { + // CREATE NEW USER + if (true != addUserEntry(userName, usrPriv, usrEnabled)) + { + break; + } + } + } + + // All userData slots update done. Lets write the data + writeUserData(); + + return; +} +} // namespace ipmi diff --git a/user_channel/user_mgmt.hpp b/user_channel/user_mgmt.hpp new file mode 100644 index 0000000..16dbd31 --- /dev/null +++ b/user_channel/user_mgmt.hpp @@ -0,0 +1,295 @@ +/* +// 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. +*/ +#pragma once +#include "user_layer.hpp" + +#include <host-ipmid/ipmid-api.h> + +#include <boost/interprocess/sync/file_lock.hpp> +#include <boost/interprocess/sync/named_recursive_mutex.hpp> +#include <cstdint> +#include <ctime> +#include <sdbusplus/bus.hpp> + +namespace ipmi +{ + +using DbusUserPropVariant = + sdbusplus::message::variant<std::vector<std::string>, std::string, bool>; + +using DbusUserObjPath = sdbusplus::message::object_path; + +using DbusUserObjProperties = + std::vector<std::pair<std::string, DbusUserPropVariant>>; + +using DbusUserObjValue = std::map<std::string, DbusUserObjProperties>; + +enum class UserUpdateEvent +{ + reservedEvent, + userCreated, + userDeleted, + userRenamed, + userGrpUpdated, + userPrivUpdated, + userStateUpdated +}; + +struct UserPrivAccess +{ + uint8_t privilege; + bool ipmiEnabled; + bool linkAuthEnabled; + bool accessCallback; +}; + +struct UserInfo +{ + uint8_t userName[ipmiMaxUserName]; + UserPrivAccess userPrivAccess[ipmiMaxChannels]; + bool userEnabled; + bool userInSystem; + bool fixedUserName; +}; + +struct UsersTbl +{ + //+1 to map with UserId directly. UserId 0 is reserved. + UserInfo user[ipmiMaxUsers + 1]; +}; + +class UserAccess; + +UserAccess& getUserAccessObject(); + +class UserAccess +{ + public: + UserAccess(const UserAccess&) = delete; + UserAccess& operator=(const UserAccess&) = delete; + UserAccess(UserAccess&&) = delete; + UserAccess& operator=(UserAccess&&) = delete; + + ~UserAccess(); + UserAccess(); + + /** @brief determines valid channel + * + * @param[in] chNum - channel number + * + * @return true if valid, false otherwise + */ + static bool isValidChannel(const uint8_t& chNum); + + /** @brief determines valid userId + * + * @param[in] userId - user id + * + * @return true if valid, false otherwise + */ + static bool isValidUserId(const uint8_t& userId); + + /** @brief determines valid user privilege + * + * @param[in] priv - Privilege + * + * @return true if valid, false otherwise + */ + static bool isValidPrivilege(const uint8_t& priv); + + /** @brief determines sync index to be mapped with common-user-management + * + * @return Index which will be used as sync index + */ + static uint8_t getUsrMgmtSyncIndex(); + + /** @brief Converts system privilege to IPMI privilege + * + * @param[in] value - Privilege in string + * + * @return CommandPrivilege - IPMI privilege type + */ + static CommandPrivilege convertToIPMIPrivilege(const std::string& value); + + /** @brief Converts IPMI privilege to system privilege + * + * @param[in] value - IPMI privilege + * + * @return System privilege in string + */ + static std::string convertToSystemPrivilege(const CommandPrivilege& value); + + /** @brief determines whether user name is valid + * + * @param[in] userNameInChar - user name + * + * @return true if valid, false otherwise + */ + bool isValidUserName(const char* userNameInChar); + + /** @brief provides user id of the user + * + * @param[in] userName - user name + * + * @return user id of the user, else invalid user id (0xFF), if user not + * found + */ + uint8_t getUserId(const std::string& userName); + + /** @brief provides user information + * + * @param[in] userId - user id + * + * @return UserInfo for the specified user id + */ + UserInfo* getUserInfo(const uint8_t& userId); + + /** @brief sets user information + * + * @param[in] userId - user id + * @param[in] userInfo - user information + * + */ + void setUserInfo(const uint8_t& userId, UserInfo* userInfo); + + /** @brief provides user name + * + * @param[in] userId - user id + * @param[out] userName - user name + * + * @return IPMI_CC_OK for success, others for failure. + */ + ipmi_ret_t getUserName(const uint8_t& userId, std::string& userName); + + /** @brief to set user name + * + * @param[in] userId - user id + * @param[in] userNameInChar - user name + * + * @return IPMI_CC_OK for success, others for failure. + */ + ipmi_ret_t setUserName(const uint8_t& userId, const char* userNameInChar); + + /** @brief to set user privilege and access details + * + * @param[in] userId - user id + * @param[in] chNum - channel number + * @param[in] privAccess - privilege access + * @param[in] otherPrivUpdates - other privilege update flag to update ipmi + * enable, link authentication and access callback + * + * @return IPMI_CC_OK for success, others for failure. + */ + ipmi_ret_t setUserPrivilegeAccess(const uint8_t& userId, + const uint8_t& chNum, + const UserPrivAccess& privAccess, + const bool& otherPrivUpdates); + + /** @brief reads user management related data from configuration file + * + */ + void readUserData(); + + /** @brief writes user management related data to configuration file + * + */ + void writeUserData(); + + /** @brief Funtion which checks and reload configuration file data if + * needed. + * + */ + void checkAndReloadUserData(); + + /** @brief provides user details from D-Bus user property data + * + * @param[in] properties - D-Bus user property + * @param[out] usrGrps - user group details + * @param[out] usrPriv - user privilege + * @param[out] usrEnabled - enabled state of the user. + * + * @return 0 for success, -errno for failure. + */ + void getUserProperties(const DbusUserObjProperties& properties, + std::vector<std::string>& usrGrps, + std::string& usrPriv, bool& usrEnabled); + + /** @brief provides user details from D-Bus user object data + * + * @param[in] userObjs - D-Bus user object + * @param[out] usrGrps - user group details + * @param[out] usrPriv - user privilege + * @param[out] usrEnabled - enabled state of the user. + * + * @return 0 for success, -errno for failure. + */ + int getUserObjProperties(const DbusUserObjValue& userObjs, + std::vector<std::string>& usrGrps, + std::string& usrPriv, bool& usrEnabled); + + /** @brief function to add user entry information to the configuration + * + * @param[in] userName - user name + * @param[in] priv - privilege of the user + * @param[in] enabled - enabled state of the user + * + * @return true for success, false for failure + */ + bool addUserEntry(const std::string& userName, const std::string& priv, + const bool& enabled); + + /** @brief function to delete user entry based on user index + * + * @param[in] usrIdx - user index + * + */ + void deleteUserIndex(const size_t& usrIdx); + + /** @brief function to get users table + * + */ + UsersTbl* getUsersTblPtr(); + + std::unique_ptr<boost::interprocess::named_recursive_mutex> userMutex{ + nullptr}; + + private: + UsersTbl usersTbl; + std::vector<std::string> availablePrivileges; + std::vector<std::string> availableGroups; + sdbusplus::bus::bus bus; + std::time_t fileLastUpdatedTime; + bool signalHndlrObject = false; + boost::interprocess::file_lock sigHndlrLock; + boost::interprocess::file_lock mutexCleanupLock; + + /** @brief function to get user configuration file timestamp + * + * @return time stamp or -EIO for failure + */ + std::time_t getUpdatedFileTime(); + + /** @brief function to available system privileges and groups + * + */ + void getSystemPrivAndGroups(); + + /** @brief function to init user data from configuration & D-Bus objects + * + */ + void initUserDataFile(); +}; +} // namespace ipmi diff --git a/user_channel/usercommands.cpp b/user_channel/usercommands.cpp new file mode 100644 index 0000000..0ed5b8f --- /dev/null +++ b/user_channel/usercommands.cpp @@ -0,0 +1,473 @@ +/* +// 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 "usercommands.hpp" + +#include "apphandler.hpp" +#include "user_layer.hpp" + +#include <host-ipmid/ipmid-api.h> +#include <security/pam_appl.h> + +#include <phosphor-logging/log.hpp> +#include <regex> + +namespace ipmi +{ + +using namespace phosphor::logging; + +static constexpr uint8_t maxIpmi20PasswordSize = 20; +static constexpr uint8_t maxIpmi15PasswordSize = 16; +static constexpr uint8_t disableUser = 0x00; +static constexpr uint8_t enableUser = 0x01; +static constexpr uint8_t setPassword = 0x02; +static constexpr uint8_t testPassword = 0x03; + +struct SetUserAccessReq +{ +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t chNum : 4; + uint8_t ipmiEnabled : 1; + uint8_t linkAuthEnabled : 1; + uint8_t accessCallback : 1; + uint8_t bitsUpdate : 1; + uint8_t userId : 6; + uint8_t reserved1 : 2; + uint8_t privilege : 4; + uint8_t reserved2 : 4; + uint8_t sessLimit : 4; // optional byte 4 + uint8_t reserved3 : 4; +#endif +#if BYTE_ORDER == BIG_ENDIAN + uint8_t bitsUpdate : 1; + uint8_t accessCallback : 1; + uint8_t linkAuthEnabled : 1; + uint8_t ipmiEnabled : 1; + uint8_t chNum : 4; + uint8_t reserved1 : 2; + uint8_t userId : 6; + uint8_t reserved2 : 4; + uint8_t privilege : 4; + uint8_t reserved3 : 4; + uint8_t sessLimit : 4; // optional byte 4 +#endif + +} __attribute__((packed)); + +struct GetUserAccessReq +{ +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t chNum : 4; + uint8_t reserved1 : 4; + uint8_t userId : 6; + uint8_t reserved2 : 2; +#endif +#if BYTE_ORDER == BIG_ENDIAN + uint8_t reserved1 : 4; + uint8_t chNum : 4; + uint8_t reserved2 : 2; + uint8_t userId : 6; +#endif +} __attribute__((packed)); + +struct GetUserAccessResp +{ +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t maxChUsers : 6; + uint8_t reserved1 : 2; + uint8_t enabledUsers : 6; + uint8_t enabledStatus : 2; + uint8_t fixedUsers : 6; + uint8_t reserved2 : 2; +#endif +#if BYTE_ORDER == BIG_ENDIAN + uint8_t reserved1 : 2; + uint8_t maxChUsers : 6; + uint8_t enabledStatus : 2; + uint8_t enabledUsers : 6; + uint8_t reserved2 : 2; + uint8_t fixedUsers : 6; +#endif + PrivAccess privAccess; +} __attribute__((packed)); + +struct SetUserNameReq +{ +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t userId : 6; + uint8_t reserved1 : 2; +#endif +#if BYTE_ORDER == BIG_ENDIAN + uint8_t reserved1 : 2; + uint8_t userId : 6; +#endif + uint8_t userName[16]; +} __attribute__((packed)); + +struct GetUserNameReq +{ +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t userId : 6; + uint8_t reserved1 : 2; +#endif +#if BYTE_ORDER == BIG_ENDIAN + uint8_t reserved1 : 2; + uint8_t userId : 6; +#endif +} __attribute__((packed)); + +struct GetUserNameResp +{ + uint8_t userName[16]; +} __attribute__((packed)); + +struct SetUserPasswordReq +{ +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t userId : 6; + uint8_t reserved1 : 1; + uint8_t ipmi20 : 1; + uint8_t operation : 2; + uint8_t reserved2 : 6; +#endif +#if BYTE_ORDER == BIG_ENDIAN + uint8_t ipmi20 : 1; + uint8_t reserved1 : 1; + uint8_t userId : 6; + uint8_t reserved2 : 6; + uint8_t operation : 2; +#endif + uint8_t userPassword[maxIpmi20PasswordSize]; +} __attribute__((packed)); + +ipmi_ret_t ipmiSetUserAccess(ipmi_netfn_t netfn, ipmi_cmd_t cmd, + ipmi_request_t request, ipmi_response_t response, + ipmi_data_len_t dataLen, ipmi_context_t context) +{ + const SetUserAccessReq* req = static_cast<SetUserAccessReq*>(request); + size_t reqLength = *dataLen; + + if (!(reqLength == sizeof(*req) || + (reqLength == (sizeof(*req) - sizeof(uint8_t) /* skip optional*/)))) + { + log<level::DEBUG>("Set user access - Invalid Length"); + return IPMI_CC_REQ_DATA_LEN_INVALID; + } + if (req->reserved1 != 0 || req->reserved2 != 0 || req->reserved3 != 0 || + req->sessLimit != 0 || + (!ipmiUserIsValidChannel(req->chNum) || + (!ipmiUserIsValidPrivilege(req->privilege)))) + // TODO: Need to check for session support and return invalid field in + // request + { + log<level::DEBUG>("Set user access - Invalid field in request"); + return IPMI_CC_INVALID_FIELD_REQUEST; + } + if (!ipmiUserIsValidUserId(req->userId)) + { + log<level::DEBUG>("Set user access - Parameter out of range"); + return IPMI_CC_PARM_OUT_OF_RANGE; + } + // TODO: Determine the Channel number 0xE (Self Channel number ?) + uint8_t chNum = req->chNum; + PrivAccess privAccess = {0}; + if (req->bitsUpdate) + { + privAccess.ipmiEnabled = req->ipmiEnabled; + privAccess.linkAuthEnabled = req->linkAuthEnabled; + privAccess.accessCallback = req->accessCallback; + } + privAccess.privilege = req->privilege; + ipmiUserSetPrivilegeAccess(req->userId, chNum, privAccess, req->bitsUpdate); + + return IPMI_CC_OK; +} + +ipmi_ret_t ipmiGetUserAccess(ipmi_netfn_t netfn, ipmi_cmd_t cmd, + ipmi_request_t request, ipmi_response_t response, + ipmi_data_len_t dataLen, ipmi_context_t context) +{ + const GetUserAccessReq* req = static_cast<GetUserAccessReq*>(request); + size_t reqLength = *dataLen; + + *dataLen = 0; + + if (reqLength != sizeof(*req)) + { + log<level::DEBUG>("Get user access - Invalid Length"); + return IPMI_CC_REQ_DATA_LEN_INVALID; + } + if (req->reserved1 != 0 || req->reserved2 != 0 || + (!ipmiUserIsValidChannel(req->chNum))) + // TODO: Need to check for session support and return invalid field in + // request + { + log<level::DEBUG>("Get user access - Invalid field in request"); + return IPMI_CC_INVALID_FIELD_REQUEST; + } + if (!ipmiUserIsValidUserId(req->userId)) + { + log<level::DEBUG>("Get user access - Parameter out of range"); + return IPMI_CC_PARM_OUT_OF_RANGE; + } + + uint8_t maxChUsers = 0, enabledUsers = 0, fixedUsers = 0; + bool enabledState = false; + // TODO: Determine the Channel number 0xE (Self Channel number ?) + uint8_t chNum = req->chNum; + GetUserAccessResp* resp = static_cast<GetUserAccessResp*>(response); + + std::fill(reinterpret_cast<uint8_t*>(resp), + reinterpret_cast<uint8_t*>(resp) + sizeof(*resp), 0); + + ipmiUserGetAllCounts(maxChUsers, enabledUsers, fixedUsers); + resp->maxChUsers = maxChUsers; + resp->enabledUsers = enabledUsers; + resp->fixedUsers = fixedUsers; + + ipmiUserCheckEnabled(req->userId, enabledState); + resp->enabledStatus = enabledState ? userIdEnabledViaSetPassword + : userIdDisabledViaSetPassword; + ipmiUserGetPrivilegeAccess(req->userId, chNum, resp->privAccess); + *dataLen = sizeof(*resp); + + return IPMI_CC_OK; +} + +ipmi_ret_t ipmiSetUserName(ipmi_netfn_t netfn, ipmi_cmd_t cmd, + ipmi_request_t request, ipmi_response_t response, + ipmi_data_len_t dataLen, ipmi_context_t context) +{ + const SetUserNameReq* req = static_cast<SetUserNameReq*>(request); + size_t reqLength = *dataLen; + *dataLen = 0; + + if (reqLength != sizeof(*req)) + { + log<level::DEBUG>("Set user name - Invalid Length"); + return IPMI_CC_REQ_DATA_LEN_INVALID; + } + if (req->reserved1) + { + return IPMI_CC_INVALID_FIELD_REQUEST; + } + if (!ipmiUserIsValidUserId(req->userId)) + { + log<level::DEBUG>("Set user name - Invalid user id"); + return IPMI_CC_PARM_OUT_OF_RANGE; + } + + return ipmiUserSetUserName(req->userId, + reinterpret_cast<const char*>(req->userName)); +} + +/** @brief implementes the get user name command + * @param[in] netfn - specifies netfn. + * @param[in] cmd - specifies cmd number. + * @param[in] request - pointer to request data. + * @param[in, out] dataLen - specifies request data length, and returns + * response data length. + * @param[in] context - ipmi context. + * @returns ipmi completion code. + */ +ipmi_ret_t ipmiGetUserName(ipmi_netfn_t netfn, ipmi_cmd_t cmd, + ipmi_request_t request, ipmi_response_t response, + ipmi_data_len_t dataLen, ipmi_context_t context) +{ + const GetUserNameReq* req = static_cast<GetUserNameReq*>(request); + size_t reqLength = *dataLen; + + *dataLen = 0; + + if (reqLength != sizeof(*req)) + { + log<level::DEBUG>("Get user name - Invalid Length"); + return IPMI_CC_REQ_DATA_LEN_INVALID; + } + + std::string userName; + if (ipmiUserGetUserName(req->userId, userName) != IPMI_CC_OK) + { // Invalid User ID + log<level::DEBUG>("User Name not found", + entry("USER-ID:%d", (uint8_t)req->userId)); + return IPMI_CC_PARM_OUT_OF_RANGE; + } + GetUserNameResp* resp = static_cast<GetUserNameResp*>(response); + std::fill(reinterpret_cast<uint8_t*>(resp), + reinterpret_cast<uint8_t*>(resp) + sizeof(*resp), 0); + userName.copy(reinterpret_cast<char*>(resp->userName), + sizeof(resp->userName), 0); + *dataLen = sizeof(*resp); + + return IPMI_CC_OK; +} + +int pamFunctionConversation(int numMsg, const struct pam_message** msg, + struct pam_response** resp, void* appdataPtr) +{ + if (appdataPtr == nullptr) + { + return PAM_AUTH_ERR; + } + size_t passSize = std::strlen(reinterpret_cast<char*>(appdataPtr)) + 1; + char* pass = reinterpret_cast<char*>(malloc(passSize)); + std::strncpy(pass, reinterpret_cast<char*>(appdataPtr), passSize); + + *resp = reinterpret_cast<pam_response*>( + calloc(numMsg, sizeof(struct pam_response))); + + for (int i = 0; i < numMsg; ++i) + { + if (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF) + { + continue; + } + resp[i]->resp = pass; + } + return PAM_SUCCESS; +} + +bool 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 + + if (pam_start("passwd", username, &localConversation, &localAuthHandle) != + PAM_SUCCESS) + { + return false; + } + int retval = pam_chauthtok(localAuthHandle, PAM_SILENT); + + if (retval != PAM_SUCCESS) + { + if (retval == PAM_AUTHTOK_ERR) + { + log<level::DEBUG>("Authentication Failure"); + } + else + { + log<level::DEBUG>("pam_chauthtok returned failure", + entry("ERROR=%d", retval)); + } + pam_end(localAuthHandle, retval); + return false; + } + if (pam_end(localAuthHandle, PAM_SUCCESS) != PAM_SUCCESS) + { + return false; + } + return true; +} + +/** @brief implementes the set user password command + * @param[in] netfn - specifies netfn. + * @param[in] cmd - specifies cmd number. + * @param[in] request - pointer to request data. + * @param[in, out] dataLen - specifies request data length, and returns + * response data length. + * @param[in] context - ipmi context. + * @returns ipmi completion code. + */ +ipmi_ret_t ipmiSetUserPassword(ipmi_netfn_t netfn, ipmi_cmd_t cmd, + ipmi_request_t request, ipmi_response_t response, + ipmi_data_len_t dataLen, ipmi_context_t context) +{ + const SetUserPasswordReq* req = static_cast<SetUserPasswordReq*>(request); + size_t reqLength = *dataLen; + // subtract 2 bytes header to know the password length - including NULL + uint8_t passwordLength = *dataLen - 2; + *dataLen = 0; + + // verify input length based on operation. Required password size is 20 + // bytes as we support only IPMI 2.0, but in order to be compatible with + // tools, accept 16 bytes of password size too. + if (reqLength < 2 || + // If enable / disable user, reqLength has to be >=2 & <= 22 + ((req->operation == disableUser || req->operation == enableUser) && + ((reqLength < 2) || (reqLength > sizeof(SetUserPasswordReq))))) + { + log<level::DEBUG>("Invalid Length"); + return IPMI_CC_REQ_DATA_LEN_INVALID; + } + // If set / test password then password length has to be 16 or 20 bytes + if (((req->operation == setPassword) || (req->operation == testPassword)) && + ((passwordLength != maxIpmi20PasswordSize) && + (passwordLength != maxIpmi15PasswordSize))) + { + log<level::DEBUG>("Invalid Length"); + return IPMI_CC_REQ_DATA_LEN_INVALID; + } + + std::string userName; + if (ipmiUserGetUserName(req->userId, userName) != IPMI_CC_OK) + { + log<level::DEBUG>("User Name not found", + entry("USER-ID:%d", (uint8_t)req->userId)); + return IPMI_CC_PARM_OUT_OF_RANGE; + } + if (req->operation == setPassword) + { + std::string passwd; + passwd.assign(reinterpret_cast<const char*>(req->userPassword), 0, + maxIpmi20PasswordSize); + if (!std::regex_match(passwd.c_str(), + std::regex("[a-zA-z_0-9][a-zA-Z_0-9,?:`!\"]*"))) + { + log<level::DEBUG>("Invalid password fields", + entry("USER-ID:%d", (uint8_t)req->userId)); + return IPMI_CC_INVALID_FIELD_REQUEST; + } + if (!pamUpdatePasswd(userName.c_str(), passwd.c_str())) + { + log<level::DEBUG>("Failed to update password", + entry("USER-ID:%d", (uint8_t)req->userId)); + return IPMI_CC_UNSPECIFIED_ERROR; + } + } + else + { + // TODO: test the password by reading the encrypted file + log<level::ERR>( + "Other operations not implemented - TODO yet to implement"); + return IPMI_CC_INVALID_FIELD_REQUEST; + } + return IPMI_CC_OK; +} + +void registerUserIpmiFunctions() +{ + ipmiUserInit(); + ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_USER_ACCESS, NULL, + ipmiSetUserAccess, PRIVILEGE_ADMIN); + + ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_USER_ACCESS, NULL, + ipmiGetUserAccess, PRIVILEGE_OPERATOR); + + ipmi_register_callback(NETFUN_APP, IPMI_CMD_GET_USER_NAME, NULL, + ipmiGetUserName, PRIVILEGE_OPERATOR); + + ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_USER_NAME, NULL, + ipmiSetUserName, PRIVILEGE_ADMIN); + + ipmi_register_callback(NETFUN_APP, IPMI_CMD_SET_USER_PASSWORD, NULL, + ipmiSetUserPassword, PRIVILEGE_ADMIN); + + return; +} +} // namespace ipmi diff --git a/user_channel/usercommands.hpp b/user_channel/usercommands.hpp new file mode 100644 index 0000000..ee33b5a --- /dev/null +++ b/user_channel/usercommands.hpp @@ -0,0 +1,36 @@ +/* +// 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. +*/ + +#pragma once +#include <cstdint> + +namespace ipmi +{ +// IPMI commands for user command NETFN:APP. +enum ipmi_netfn_user_cmds +{ + IPMI_CMD_SET_USER_ACCESS = 0x43, + IPMI_CMD_GET_USER_ACCESS = 0x44, + IPMI_CMD_SET_USER_NAME = 0x45, + IPMI_CMD_GET_USER_NAME = 0x46, + IPMI_CMD_SET_USER_PASSWORD = 0x47, +}; + +static constexpr uint8_t userIdEnabledViaSetPassword = 0x1; +static constexpr uint8_t userIdDisabledViaSetPassword = 0x2; + +void registerUserIpmiFunctions(); +} // namespace ipmi |