summaryrefslogtreecommitdiffstats
path: root/user_channel/user_mgmt.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'user_channel/user_mgmt.cpp')
-rw-r--r--user_channel/user_mgmt.cpp1304
1 files changed, 1304 insertions, 0 deletions
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
OpenPOWER on IntegriCloud