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