blob: 53b34f8332b2d02e93ae37d1f26fdca441d7f4b9 [file] [log] [blame]
/*
// 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 "channel_layer.hpp"
#include "user_layer.hpp"
#include <security/pam_appl.h>
#include <ipmid/api.hpp>
#include <phosphor-logging/lg2.hpp>
#include <regex>
namespace ipmi
{
static constexpr uint8_t enableOperation = 0x00;
static constexpr uint8_t disableOperation = 0x01;
/** @brief implements the set user access command
* @param ctx - IPMI context pointer (for channel)
* @param channel - channel number
* @param ipmiEnabled - indicates ipmi messaging state
* @param linkAuthEnabled - indicates link authentication state
* @param accessCallback - indicates callback state
* @param bitsUpdate - indicates update request
* @param userId - user id
* @param reserved1 - skip 2 bits
* @param privilege - user privilege
* @param reserved2 - skip 4 bits
* @param sessionLimit - optional - unused for now
*
* @returns ipmi completion code
*/
ipmi::RspType<> ipmiSetUserAccess(ipmi::Context::ptr ctx, uint4_t channel,
uint1_t ipmiEnabled, uint1_t linkAuthEnabled,
uint1_t accessCallback, uint1_t bitsUpdate,
uint6_t userId, uint2_t reserved1,
uint4_t privilege, uint4_t reserved2,
std::optional<uint8_t> sessionLimit)
{
uint8_t sessLimit = sessionLimit.value_or(0);
if (reserved1 || reserved2 || sessLimit ||
!ipmiUserIsValidPrivilege(static_cast<uint8_t>(privilege)))
{
lg2::debug("Set user access - Invalid field in request");
return ipmi::responseInvalidFieldRequest();
}
uint8_t chNum = convertCurrentChannelNum(static_cast<uint8_t>(channel),
ctx->channel);
if (!isValidChannel(chNum))
{
lg2::debug("Set user access - Invalid channel request");
return ipmi::response(invalidChannel);
}
if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
{
lg2::debug("Set user access - No support on channel");
return ipmi::response(ccActionNotSupportedForChannel);
}
if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
{
lg2::debug("Set user access - Parameter out of range");
return ipmi::responseParmOutOfRange();
}
PrivAccess privAccess = {};
if (bitsUpdate)
{
privAccess.ipmiEnabled = static_cast<uint8_t>(ipmiEnabled);
privAccess.linkAuthEnabled = static_cast<uint8_t>(linkAuthEnabled);
privAccess.accessCallback = static_cast<uint8_t>(accessCallback);
}
privAccess.privilege = static_cast<uint8_t>(privilege);
return ipmi::response(
ipmiUserSetPrivilegeAccess(static_cast<uint8_t>(userId), chNum,
privAccess, static_cast<bool>(bitsUpdate)));
}
/** @brief implements the set user access command
* @param ctx - IPMI context pointer (for channel)
* @param channel - channel number
* @param reserved1 - skip 4 bits
* @param userId - user id
* @param reserved2 - skip 2 bits
*
* @returns ipmi completion code plus response data
* - maxChUsers - max channel users
* - reserved1 - skip 2 bits
* - enabledUsers - enabled users count
* - enabledStatus - enabled status
* - fixedUsers - fixed users count
* - reserved2 - skip 2 bits
* - privilege - user privilege
* - ipmiEnabled - ipmi messaging state
* - linkAuthEnabled - link authenticatin state
* - accessCallback - callback state
* - reserved - skip 1 bit
*/
ipmi::RspType<uint6_t, // max channel users
uint2_t, // reserved1
uint6_t, // enabled users count
uint2_t, // enabled status
uint6_t, // fixed users count
uint2_t, // reserved2
uint4_t, // privilege
uint1_t, // ipmi messaging state
uint1_t, // link authentication state
uint1_t, // access callback state
uint1_t // reserved3
>
ipmiGetUserAccess(ipmi::Context::ptr ctx, uint4_t channel,
uint4_t reserved1,
uint6_t userId, uint2_t reserved2)
{
uint8_t chNum = convertCurrentChannelNum(static_cast<uint8_t>(channel),
ctx->channel);
if (reserved1 || reserved2 || !isValidChannel(chNum))
{
lg2::debug("Get user access - Invalid field in request");
return ipmi::responseInvalidFieldRequest();
}
if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
{
lg2::debug("Get user access - No support on channel");
return ipmi::response(ccActionNotSupportedForChannel);
}
if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
{
lg2::debug("Get user access - Parameter out of range");
return ipmi::responseParmOutOfRange();
}
uint8_t maxChUsers = 0, enabledUsers = 0, fixedUsers = 0;
ipmi::Cc retStatus;
retStatus = ipmiUserGetAllCounts(maxChUsers, enabledUsers, fixedUsers);
if (retStatus != ccSuccess)
{
return ipmi::response(retStatus);
}
bool enabledState = false;
retStatus = ipmiUserCheckEnabled(static_cast<uint8_t>(userId),
enabledState);
if (retStatus != ccSuccess)
{
return ipmi::response(retStatus);
}
uint2_t enabledStatus = enabledState ? userIdEnabledViaSetPassword
: userIdDisabledViaSetPassword;
PrivAccess privAccess{};
retStatus = ipmiUserGetPrivilegeAccess(static_cast<uint8_t>(userId), chNum,
privAccess);
if (retStatus != ccSuccess)
{
return ipmi::response(retStatus);
}
constexpr uint2_t res2Bits = 0;
return ipmi::responseSuccess(
static_cast<uint6_t>(maxChUsers), res2Bits,
static_cast<uint6_t>(enabledUsers), enabledStatus,
static_cast<uint6_t>(fixedUsers), res2Bits,
static_cast<uint4_t>(privAccess.privilege),
static_cast<uint1_t>(privAccess.ipmiEnabled),
static_cast<uint1_t>(privAccess.linkAuthEnabled),
static_cast<uint1_t>(privAccess.accessCallback),
static_cast<uint1_t>(privAccess.reserved));
}
/** @brief implementes the get user name command
* @param[in] ctx - ipmi command context
* @param[in] userId - 6-bit user ID
* @param[in] reserved - 2-bits reserved
* @param[in] name - 16-byte array for username
* @returns ipmi response
*/
ipmi::RspType<>
ipmiSetUserName([[maybe_unused]] ipmi::Context::ptr ctx, uint6_t id,
uint2_t reserved,
const std::array<uint8_t, ipmi::ipmiMaxUserName>& name)
{
if (reserved)
{
return ipmi::responseInvalidFieldRequest();
}
uint8_t userId = static_cast<uint8_t>(id);
if (!ipmiUserIsValidUserId(userId))
{
lg2::debug("Set user name - Invalid user id");
return ipmi::responseParmOutOfRange();
}
size_t nameLen = strnlen(reinterpret_cast<const char*>(name.data()),
ipmi::ipmiMaxUserName);
const std::string strUserName(reinterpret_cast<const char*>(name.data()),
nameLen);
ipmi::Cc res = ipmiUserSetUserName(userId, strUserName);
return ipmi::response(res);
}
/** @brief implementes the get user name command
* @param[in] ctx - ipmi command context
* @param[in] userId - 6-bit user ID
* @param[in] reserved - 2-bits reserved
* @returns ipmi response with 16-byte username
*/
ipmi::RspType<std::array<uint8_t, ipmi::ipmiMaxUserName>> // user name
ipmiGetUserName([[maybe_unused]] ipmi::Context::ptr ctx, uint6_t id,
uint2_t reserved)
{
if (reserved)
{
return ipmi::responseInvalidFieldRequest();
}
uint8_t userId = static_cast<uint8_t>(id);
std::string userName;
if (ipmiUserGetUserName(userId, userName) != ccSuccess)
{ // Invalid User ID
lg2::debug("User Name not found, user Id: {USER_ID}", "USER_ID",
userId);
return ipmi::responseParmOutOfRange();
}
// copy the std::string into a fixed array
if (userName.size() > ipmi::ipmiMaxUserName)
{
return ipmi::responseUnspecifiedError();
}
std::array<uint8_t, ipmi::ipmiMaxUserName> userNameFixed;
std::fill(userNameFixed.begin(), userNameFixed.end(), 0);
std::copy(userName.begin(), userName.end(), userNameFixed.begin());
return ipmi::responseSuccess(std::move(userNameFixed));
}
/** @brief implementes the get user name command
* @param[in] ctx - ipmi command context
* @param[in] userId - 6-bit user ID
* @param[in] reserved - 2-bits reserved
* @returns ipmi response with 16-byte username
*/
ipmi::RspType<> // user name
ipmiSetUserPassword([[maybe_unused]] ipmi::Context::ptr ctx, uint6_t id,
bool reserved1, bool pwLen20, uint2_t operation,
uint6_t reserved2, SecureBuffer& userPassword)
{
if (reserved1 || reserved2)
{
lg2::debug("Invalid data field in request");
return ipmi::responseInvalidFieldRequest();
}
static constexpr uint2_t opDisableUser = 0x00;
static constexpr uint2_t opEnableUser = 0x01;
static constexpr uint2_t opSetPassword = 0x02;
static constexpr uint2_t opTestPassword = 0x03;
// If set / test password operation then password size has to be 16 or 20
// bytes based on the password size bit
if (((operation == opSetPassword) || (operation == opTestPassword)) &&
((pwLen20 && (userPassword.size() != maxIpmi20PasswordSize)) ||
(!pwLen20 && (userPassword.size() != maxIpmi15PasswordSize))))
{
lg2::debug("Invalid Length");
return ipmi::responseReqDataLenInvalid();
}
size_t passwordLength = userPassword.size();
uint8_t userId = static_cast<uint8_t>(id);
std::string userName;
if (ipmiUserGetUserName(userId, userName) != ccSuccess)
{
lg2::debug("User Name not found, user Id: {USER_ID}", "USER_ID",
userId);
return ipmi::responseParmOutOfRange();
}
if (operation == opSetPassword)
{
// turn the non-nul terminated SecureBuffer into a SecureString
SecureString password(
reinterpret_cast<const char*>(userPassword.data()), passwordLength);
ipmi::Cc res = ipmiUserSetUserPassword(userId, password.data());
return ipmi::response(res);
}
else if (operation == opEnableUser || operation == opDisableUser)
{
ipmi::Cc res = ipmiUserUpdateEnabledState(userId,
static_cast<bool>(operation));
return ipmi::response(res);
}
else if (operation == opTestPassword)
{
SecureString password = ipmiUserGetPassword(userName);
// extend with zeros, if needed
if (password.size() < passwordLength)
{
password.resize(passwordLength, '\0');
}
SecureString testPassword(
reinterpret_cast<const char*>(userPassword.data()), passwordLength);
// constant time string compare: always compare exactly as many bytes
// as the length of the input, resizing the actual password to match,
// maintaining a knowledge if the sizes differed originally
static const std::array<char, maxIpmi20PasswordSize> empty = {'\0'};
size_t cmpLen = testPassword.size();
bool pwLenDiffers = password.size() != cmpLen;
const char* cmpPassword = nullptr;
if (pwLenDiffers)
{
cmpPassword = empty.data();
}
else
{
cmpPassword = password.data();
}
bool pwBad = CRYPTO_memcmp(cmpPassword, testPassword.data(), cmpLen);
pwBad |= pwLenDiffers;
if (pwBad)
{
lg2::debug("Test password failed, user Id: {USER_ID}", "USER_ID",
userId);
static constexpr ipmi::Cc ipmiCCPasswdFailMismatch = 0x80;
return ipmi::response(ipmiCCPasswdFailMismatch);
}
return ipmi::responseSuccess();
}
return ipmi::responseInvalidFieldRequest();
}
/** @brief implements the get channel authentication command
* @param ctx - IPMI context pointer (for channel)
* @param extData - get IPMI 2.0 extended data
* @param reserved1 - skip 3 bits
* @param chNum - channel number to get info about
* @param reserved2 - skip 4 bits
* @param privLevel - requested privilege level
* @returns ipmi completion code plus response data
* - channel number
* - rmcpAuthTypes - RMCP auth types (IPMI 1.5)
* - reserved1
* - extDataSupport - true for IPMI 2.0 extensions
* - anonymousLogin - true for anonymous login enabled
* - nullUsers - true for null user names enabled
* - nonNullUsers - true for non-null usernames enabled
* - userAuth - false for user authentication enabled
* - perMessageAuth - false for per message authentication enabled
* - KGStatus - true for Kg required for authentication
* - reserved2
* - rmcp - RMCP (IPMI 1.5) connection support
* - rmcpp - RMCP+ (IPMI 2.0) connection support
* - reserved3
* - oemID - OEM IANA of any OEM auth support
* - oemAuxillary - OEM data for auth
*/
ipmi::RspType<uint8_t, // channel number
uint6_t, // rmcpAuthTypes
bool, // reserved1
bool, // extDataSupport
bool, // anonymousLogin
bool, // nullUsers
bool, // nonNullUsers
bool, // userAuth
bool, // perMessageAuth
bool, // KGStatus
uint2_t, // reserved2
bool, // rmcp
bool, // rmcpp
uint6_t, // reserved3
uint24_t, // oemID
uint8_t // oemAuxillary
>
ipmiGetChannelAuthenticationCapabilities(ipmi::Context::ptr ctx,
uint4_t chNum, uint3_t reserved1,
[[maybe_unused]] bool extData,
uint4_t privLevel,
uint4_t reserved2)
{
uint8_t channel = convertCurrentChannelNum(static_cast<uint8_t>(chNum),
ctx->channel);
if (reserved1 || reserved2 || !isValidChannel(channel) ||
!isValidPrivLimit(static_cast<uint8_t>(privLevel)))
{
lg2::debug("Get channel auth capabilities - Invalid field in request");
return ipmi::responseInvalidFieldRequest();
}
if (getChannelSessionSupport(channel) == EChannelSessSupported::none)
{
lg2::debug("Get channel auth capabilities - No support on channel");
return ipmi::response(ccActionNotSupportedForChannel);
}
constexpr bool extDataSupport = true; // true for IPMI 2.0 extensions
constexpr bool reserved3 = false;
constexpr uint6_t rmcpAuthTypes = 0; // IPMI 1.5 auth types - not supported
constexpr uint2_t reserved4 = 0;
constexpr bool KGStatus = false; // Not supporting now.
constexpr bool perMessageAuth = false; // Per message auth - enabled
constexpr bool userAuth = false; // User authentication - enabled
constexpr bool nullUsers = false; // Null user names - not supported
constexpr bool anonymousLogin = false; // Anonymous login - not supported
constexpr uint6_t reserved5 = 0;
constexpr bool rmcpp = true; // IPMI 2.0 - supported
constexpr bool rmcp = false; // IPMI 1.5 - not supported
constexpr uint24_t oemID = 0;
constexpr uint8_t oemAuxillary = 0;
bool nonNullUsers = 0;
uint8_t maxChUsers = 0, enabledUsers = 0, fixedUsers = 0;
ipmi::ipmiUserGetAllCounts(maxChUsers, enabledUsers, fixedUsers);
nonNullUsers = enabledUsers > 0;
return ipmi::responseSuccess(
channel, rmcpAuthTypes, reserved3, extDataSupport, anonymousLogin,
nullUsers, nonNullUsers, userAuth, perMessageAuth, KGStatus, reserved4,
rmcp, rmcpp, reserved5, oemID, oemAuxillary);
}
/** @brief implements the set user payload access command.
* @param ctx - IPMI context pointer (for channel)
* @param channel - channel number (4 bits)
* @param reserved1 - skip 4 bits
* @param userId - user id (6 bits)
* @param operation - access ENABLE /DISABLE. (2 bits)
* @param stdPayload0 - IPMI - reserved. (1 bit)
* @param stdPayload1 - SOL. (1 bit)
* @param stdPayload2 - (1 bit)
* @param stdPayload3 - (1 bit)
* @param stdPayload4 - (1 bit)
* @param stdPayload5 - (1 bit)
* @param stdPayload6 - (1 bit)
* @param stdPayload7 - (1 bit)
* @param stdPayloadEnables2Reserved - (8 bits)
* @param oemPayload0 - (1 bit)
* @param oemPayload1 - (1 bit)
* @param oemPayload2 - (1 bit)
* @param oemPayload3 - (1 bit)
* @param oemPayload4 - (1 bit)
* @param oemPayload5 - (1 bit)
* @param oemPayload6 - (1 bit)
* @param oemPayload7 - (1 bit)
* @param oemPayloadEnables2Reserved - (8 bits)
*
* @returns IPMI completion code
*/
ipmi::RspType<> ipmiSetUserPayloadAccess(
ipmi::Context::ptr ctx,
uint4_t channel, uint4_t reserved,
uint6_t userId, uint2_t operation,
bool stdPayload0ipmiReserved, bool stdPayload1SOL, bool stdPayload2,
bool stdPayload3, bool stdPayload4, bool stdPayload5, bool stdPayload6,
bool stdPayload7,
uint8_t stdPayloadEnables2Reserved,
bool oemPayload0, bool oemPayload1, bool oemPayload2, bool oemPayload3,
bool oemPayload4, bool oemPayload5, bool oemPayload6, bool oemPayload7,
uint8_t oemPayloadEnables2Reserved)
{
auto chNum = convertCurrentChannelNum(static_cast<uint8_t>(channel),
ctx->channel);
// Validate the reserved args. Only SOL payload is supported as on date.
if (reserved || stdPayload0ipmiReserved || stdPayload2 || stdPayload3 ||
stdPayload4 || stdPayload5 || stdPayload6 || stdPayload7 ||
oemPayload0 || oemPayload1 || oemPayload2 || oemPayload3 ||
oemPayload4 || oemPayload5 || oemPayload6 || oemPayload7 ||
stdPayloadEnables2Reserved || oemPayloadEnables2Reserved ||
!isValidChannel(chNum))
{
return ipmi::responseInvalidFieldRequest();
}
if ((operation != enableOperation && operation != disableOperation))
{
return ipmi::responseInvalidFieldRequest();
}
if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
{
return ipmi::response(ccActionNotSupportedForChannel);
}
if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
{
return ipmi::responseParmOutOfRange();
}
PayloadAccess payloadAccess = {};
payloadAccess.stdPayloadEnables1[1] = stdPayload1SOL;
return ipmi::response(ipmiUserSetUserPayloadAccess(
chNum, static_cast<uint8_t>(operation), static_cast<uint8_t>(userId),
payloadAccess));
}
/** @brief implements the get user payload access command
* This command returns information about user payload enable settings
* that were set using the 'Set User Payload Access' Command.
*
* @param ctx - IPMI context pointer (for channel)
* @param channel - channel number
* @param reserved1 - skip 4 bits
* @param userId - user id
* @param reserved2 - skip 2 bits
*
* @returns IPMI completion code plus response data
* - stdPayload0ipmiReserved - IPMI payload (reserved).
* - stdPayload1SOL - SOL payload
* - stdPayload2
* - stdPayload3
* - stdPayload4
* - stdPayload5
* - stdPayload6
* - stdPayload7
* - stdPayloadEnables2Reserved - Reserved.
* - oemPayload0
* - oemPayload1
* - oemPayload2
* - oemPayload3
* - oemPayload4
* - oemPayload5
* - oemPayload6
* - oemPayload7
* - oemPayloadEnables2Reserved - Reserved
*/
ipmi::RspType<bool, // stdPayload0ipmiReserved
bool, // stdPayload1SOL
bool, // stdPayload2
bool, // stdPayload3
bool, // stdPayload4
bool, // stdPayload5
bool, // stdPayload6
bool, // stdPayload7
uint8_t, // stdPayloadEnables2Reserved
bool, // oemPayload0
bool, // oemPayload1
bool, // oemPayload2
bool, // oemPayload3
bool, // oemPayload4
bool, // oemPayload5
bool, // oemPayload6
bool, // oemPayload7
uint8_t // oemPayloadEnables2Reserved
>
ipmiGetUserPayloadAccess(ipmi::Context::ptr ctx,
uint4_t channel, uint4_t reserved1,
uint6_t userId, uint2_t reserved2)
{
uint8_t chNum = convertCurrentChannelNum(static_cast<uint8_t>(channel),
ctx->channel);
if (reserved1 || reserved2 || !isValidChannel(chNum))
{
return ipmi::responseInvalidFieldRequest();
}
if (getChannelSessionSupport(chNum) == EChannelSessSupported::none)
{
return ipmi::response(ccActionNotSupportedForChannel);
}
if (!ipmiUserIsValidUserId(static_cast<uint8_t>(userId)))
{
return ipmi::responseParmOutOfRange();
}
ipmi::Cc retStatus;
PayloadAccess payloadAccess = {};
retStatus = ipmiUserGetUserPayloadAccess(
chNum, static_cast<uint8_t>(userId), payloadAccess);
if (retStatus != ccSuccess)
{
return ipmi::response(retStatus);
}
constexpr uint8_t res8bits = 0;
return ipmi::responseSuccess(payloadAccess.stdPayloadEnables1.test(0),
payloadAccess.stdPayloadEnables1.test(1),
payloadAccess.stdPayloadEnables1.test(2),
payloadAccess.stdPayloadEnables1.test(3),
payloadAccess.stdPayloadEnables1.test(4),
payloadAccess.stdPayloadEnables1.test(5),
payloadAccess.stdPayloadEnables1.test(6),
payloadAccess.stdPayloadEnables1.test(7),
res8bits,
payloadAccess.oemPayloadEnables1.test(0),
payloadAccess.oemPayloadEnables1.test(1),
payloadAccess.oemPayloadEnables1.test(2),
payloadAccess.oemPayloadEnables1.test(3),
payloadAccess.oemPayloadEnables1.test(4),
payloadAccess.oemPayloadEnables1.test(5),
payloadAccess.oemPayloadEnables1.test(6),
payloadAccess.oemPayloadEnables1.test(7),
res8bits);
}
void registerUserIpmiFunctions() __attribute__((constructor));
void registerUserIpmiFunctions()
{
post_work([]() { ipmiUserInit(); });
ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
ipmi::app::cmdSetUserAccessCommand,
ipmi::Privilege::Admin, ipmiSetUserAccess);
ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
ipmi::app::cmdGetUserAccessCommand,
ipmi::Privilege::Admin, ipmiGetUserAccess);
ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
ipmi::app::cmdGetUserNameCommand,
ipmi::Privilege::Admin, ipmiGetUserName);
ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
ipmi::app::cmdSetUserName, ipmi::Privilege::Admin,
ipmiSetUserName);
ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
ipmi::app::cmdSetUserPasswordCommand,
ipmi::Privilege::Admin, ipmiSetUserPassword);
ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
ipmi::app::cmdGetChannelAuthCapabilities,
ipmi::Privilege::Callback,
ipmiGetChannelAuthenticationCapabilities);
ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
ipmi::app::cmdSetUserPayloadAccess,
ipmi::Privilege::Admin, ipmiSetUserPayloadAccess);
ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnApp,
ipmi::app::cmdGetUserPayloadAccess,
ipmi::Privilege::Operator, ipmiGetUserPayloadAccess);
return;
}
} // namespace ipmi