summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com>2018-03-12 23:42:34 +0530
committerVernon Mauery <vernon.mauery@linux.intel.com>2018-11-20 00:05:33 +0000
commit5a6b63694fecdd0b4df40033f31e6c113380f3f1 (patch)
tree2a1aba56ee896c6a7764ee7b5fae11f64b49c3f3
parentad359eb03bee9ab3886d43e59d7b2ee877f84184 (diff)
downloadphosphor-host-ipmid-5a6b63694fecdd0b4df40033f31e6c113380f3f1.tar.gz
phosphor-host-ipmid-5a6b63694fecdd0b4df40033f31e6c113380f3f1.zip
Basic IPMI User Management Support
Squashed commit containing Basic User Management implementation Unit Test: 1. Verified both Host & NetIpmid works fine. 2. Verified user related command responses commit b46b869c0a3958a572b976b3bbaf6b5f33673778 Author: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Date: Tue Aug 7 15:30:32 2018 +0530 Fix static objects to directly link user layer User layer library has to be directly linked with netipmid in order to be used in RAKP commands. Hence user layer library should not initialize the static bus objects in file scope, as ipmid_get_sd_bus_connection() won't be available during this time. Hence moved it under function scope and initialize it later. Unit test: Made sure, with this change it is loaded perfectly under phosphor-ipmi-host & phosphor-ipmi-net and responding to all user commands Change-Id: Id3f2e06580ca41b0347176ca33e011bf7b048c6a Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> commit dc60516c45234379a30ad8b03b1cbe53978faeae Author: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Date: Tue Aug 7 15:22:37 2018 +0530 Fix to use proper user index for set user name Fix to use proper user index, instead of bailing out on first empty index. If user creation is requested on random index with previous index empty, then signal handler updates the data in first empty index, instead of traversing the user list fully. Fix added to mark the first free index, and still search for match to skip if found. Unit Test: Verified that user added in random index shows in that index using ipmitool set user name command. Change-Id: I30d9b884a5bae98b243ccf8ba7da194ef81355e6 Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> commit 0b9a81a1261b08ea13ad8777d0d80dad937e9972 Author: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Date: Fri Jul 6 15:57:02 2018 +0530 Converting json store to array for user config Addressed comments to convert to array instead of key value pair. Testing: Performed basic testing of user management commands in both host & netipmid, by repeatedly restarting the same. Change-Id: I2e51ded3e2299fa196d868213950d96c72886358 Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> commit 182c945657b29acaeb13fc44129e0ed929013e59 Author: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Date: Sat Jun 30 00:07:28 2018 +0530 Fix D-Bus paths for user management Fix D-Bus path as per upstream code in user management Change-Id: Idb22bee4365520f3d58e70ff61b02d6f6512d707 Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> commit cd5e22b16f8805dda396311b06994a9e021e97e9 Author: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Date: Thu Jun 28 06:22:27 2018 +0530 Json format to store user config & cleanup Code updated to use json format to store and read the user configuration data. Few basic cleanup's performed. Change-Id: I7005f4f2648ccb8214312982a755ddcc6c33e64d Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> commit 95ca9a67a4b84df603db0eb6a7024e9e0e5ad342 Author: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Date: Wed May 30 15:16:47 2018 +0530 Get & Set User Access command implementation Get & Set User access command implementation Change-Id: I4f7a86de95400387c47ae556127baa1f02a703b2 Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> commit b2dea762362d06b70576dba5f45552d548792cb9 Author: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Date: Fri May 18 23:46:23 2018 +0530 User layer separation. Separated out user commands from the implementation details, by creating user layers. App Handler library and netipmid will directly rely on this user_layer.so Change-Id: Ie7d4b3a5a934e32da73e066a25da4c27485c59c8 Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> commit 63e3113e522ecc7ce6e5aa1c85de4dc9b9a65a7b Author: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Date: Mon May 7 12:00:06 2018 +0530 Updated user management ipmi support 1. Relying on Object Manager & D-Bus properties signal (except user rename, for which still relying on user rename signal) 2. Minor clean-up. Change-Id: I594e7823a2c626bb7c88ec54a3ffa89a60b09c65 Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> commit 3e7b774c842917743369569790612d74387c755e Author: AppaRao Puli <apparao.puli@intel.com> Date: Tue Apr 24 22:01:15 2018 +0530 Synchronize user manager data during ipmi startup Synchronizing the user information by reading all managed user objects from dbus and checking ipmi user data for any update/delete/add users. Change-Id: I9e1a62ec9dd186a7276d083a57e679606d635e05 Signed-off-by: AppaRao Puli <apparao.puli@intel.com> Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> commit 92298ac0f591d167b0e26b977316a2b136127778 Author: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Date: Tue Apr 24 21:34:08 2018 +0530 Minor fix: Read user enabled state in signals Fixed to read user enabled state in signal handlers. Change-Id: I0aa6c4687c16e08d8e304315e85cb65e9dbd346a Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> commit 8646d2683fc247ce02a0460f9577276eb6e0a581 Author: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Date: Wed Apr 11 22:18:44 2018 +0530 Signal handler update Support added to handle group, privilege user rename, enable / disable signal, and update the database accordingly. Change-Id: Ia33d063715a35814bbe1f9220e9609b800261e33 Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> commit fa411b2c4bbef50175b084889a4829206263ebdb Author: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Date: Mon Mar 12 23:42:34 2018 +0530 Add Basic user manager support - IPMI Add basic user manager support in IPMI Creates user through D-Bus user interface and sets password using pam_chauthok(). Lock & File reload mechanism also implmeneted. UserUpdate signal handler support added to update user in IPMI, when users are updated through different interfaces. Change-Id: I1adc538562615109189d7c19dadae0b6a109f4a5 Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com> Change-Id: I1adc538562615109189d7c19dadae0b6a109f4a5 Signed-off-by: Richard Marian Thomaiyar <richard.marian.thomaiyar@linux.intel.com>
-rw-r--r--Makefile.am2
-rw-r--r--apphandler.cpp3
-rw-r--r--user_channel/user_layer.cpp112
-rw-r--r--user_channel/user_layer.hpp143
-rw-r--r--user_channel/user_mgmt.cpp1304
-rw-r--r--user_channel/user_mgmt.hpp295
-rw-r--r--user_channel/usercommands.cpp473
-rw-r--r--user_channel/usercommands.hpp36
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
OpenPOWER on IntegriCloud