/* // 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 "config.h" #include "user_mgr.hpp" #include "file.hpp" #include "shadowlock.hpp" #include "users.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace phosphor { namespace user { static constexpr const char* passwdFileName = "/etc/passwd"; static constexpr size_t ipmiMaxUserNameLen = 16; static constexpr size_t systemMaxUserNameLen = 30; static constexpr const char* grpSsh = "ssh"; static constexpr int success = 0; static constexpr int failure = -1; // pam modules related static constexpr const char* pamTally2 = "pam_tally2.so"; static constexpr const char* pamCrackLib = "pam_cracklib.so"; static constexpr const char* pamPWHistory = "pam_pwhistory.so"; static constexpr const char* minPasswdLenProp = "minlen"; static constexpr const char* remOldPasswdCount = "remember"; static constexpr const char* maxFailedAttempt = "deny"; static constexpr const char* unlockTimeout = "unlock_time"; static constexpr const char* defaultPamPasswdConfigFile = "/etc/pam.d/common-password"; static constexpr const char* defaultPamAuthConfigFile = "/etc/pam.d/common-auth"; // Object Manager related static constexpr const char* ldapMgrObjBasePath = "/xyz/openbmc_project/user/ldap"; // 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"; using namespace phosphor::logging; using InsufficientPermission = sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission; using InternalFailure = sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; using InvalidArgument = sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; using UserNameExists = sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists; using UserNameDoesNotExist = sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist; using UserNameGroupFail = sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail; using NoResource = sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource; using Argument = xyz::openbmc_project::Common::InvalidArgument; std::string getCSVFromVector(std::span vec) { if (vec.empty()) { return ""; } return std::accumulate(std::next(vec.begin()), vec.end(), vec[0], [](std::string&& val, std::string_view element) { val += ','; val += element; return val; }); } bool removeStringFromCSV(std::string& csvStr, const std::string& delStr) { std::string::size_type delStrPos = csvStr.find(delStr); if (delStrPos != std::string::npos) { // need to also delete the comma char if (delStrPos == 0) { csvStr.erase(delStrPos, delStr.size() + 1); } else { csvStr.erase(delStrPos - 1, delStr.size() + 1); } return true; } return false; } bool UserMgr::isUserExist(const std::string& userName) { if (userName.empty()) { log("User name is empty"); elog(Argument::ARGUMENT_NAME("User name"), Argument::ARGUMENT_VALUE("Null")); } if (usersList.find(userName) == usersList.end()) { return false; } return true; } void UserMgr::throwForUserDoesNotExist(const std::string& userName) { if (!isUserExist(userName)) { log("User does not exist", entry("USER_NAME=%s", userName.c_str())); elog(); } } void UserMgr::throwForUserExists(const std::string& userName) { if (isUserExist(userName)) { log("User already exists", entry("USER_NAME=%s", userName.c_str())); elog(); } } void UserMgr::throwForUserNameConstraints( const std::string& userName, const std::vector& groupNames) { if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != groupNames.end()) { if (userName.length() > ipmiMaxUserNameLen) { log("IPMI user name length limitation", entry("SIZE=%d", userName.length())); elog( xyz::openbmc_project::User::Common::UserNameGroupFail::REASON( "IPMI length")); } } if (userName.length() > systemMaxUserNameLen) { log("User name length limitation", entry("SIZE=%d", userName.length())); elog(Argument::ARGUMENT_NAME("User name"), Argument::ARGUMENT_VALUE("Invalid length")); } if (!std::regex_match(userName.c_str(), std::regex("[a-zA-z_][a-zA-Z_0-9]*"))) { log("Invalid user name", entry("USER_NAME=%s", userName.c_str())); elog(Argument::ARGUMENT_NAME("User name"), Argument::ARGUMENT_VALUE("Invalid data")); } } void UserMgr::throwForMaxGrpUserCount( const std::vector& groupNames) { if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != groupNames.end()) { if (getIpmiUsersCount() >= ipmiMaxUsers) { log("IPMI user limit reached"); elog( xyz::openbmc_project::User::Common::NoResource::REASON( "ipmi user count reached")); } } else { if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >= (maxSystemUsers - ipmiMaxUsers)) { log("Non-ipmi User limit reached"); elog( xyz::openbmc_project::User::Common::NoResource::REASON( "Non-ipmi user count reached")); } } return; } void UserMgr::throwForInvalidPrivilege(const std::string& priv) { if (!priv.empty() && (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end())) { log("Invalid privilege"); elog(Argument::ARGUMENT_NAME("Privilege"), Argument::ARGUMENT_VALUE(priv.c_str())); } } void UserMgr::throwForInvalidGroups(const std::vector& groupNames) { for (auto& group : groupNames) { if (std::find(groupsMgr.begin(), groupsMgr.end(), group) == groupsMgr.end()) { log("Invalid Group Name listed"); elog(Argument::ARGUMENT_NAME("GroupName"), Argument::ARGUMENT_VALUE(group.c_str())); } } } void UserMgr::createUser(std::string userName, std::vector groupNames, std::string priv, bool enabled) { throwForInvalidPrivilege(priv); throwForInvalidGroups(groupNames); // All user management lock has to be based on /etc/shadow // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; throwForUserExists(userName); throwForUserNameConstraints(userName, groupNames); throwForMaxGrpUserCount(groupNames); std::string groups = getCSVFromVector(groupNames); bool sshRequested = removeStringFromCSV(groups, grpSsh); // treat privilege as a group - This is to avoid using different file to // store the same. if (!priv.empty()) { if (groups.size() != 0) { groups += ","; } groups += priv; } try { executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled); } catch (const InternalFailure& e) { log("Unable to create new user"); elog(); } // Add the users object before sending out the signal sdbusplus::message::object_path tempObjPath(usersObjPath); tempObjPath /= userName; std::string userObj(tempObjPath); std::sort(groupNames.begin(), groupNames.end()); usersList.emplace( userName, std::make_unique( bus, userObj.c_str(), groupNames, priv, enabled, *this)); log("User created successfully", entry("USER_NAME=%s", userName.c_str())); return; } void UserMgr::deleteUser(std::string userName) { // All user management lock has to be based on /etc/shadow // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; throwForUserDoesNotExist(userName); try { executeUserDelete(userName.c_str()); } catch (const InternalFailure& e) { log("User delete failed", entry("USER_NAME=%s", userName.c_str())); elog(); } usersList.erase(userName); log("User deleted successfully", entry("USER_NAME=%s", userName.c_str())); return; } void UserMgr::renameUser(std::string userName, std::string newUserName) { // All user management lock has to be based on /etc/shadow // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; throwForUserDoesNotExist(userName); throwForUserExists(newUserName); throwForUserNameConstraints(newUserName, usersList[userName].get()->userGroups()); try { executeUserRename(userName.c_str(), newUserName.c_str()); } catch (const InternalFailure& e) { log("User rename failed", entry("USER_NAME=%s", userName.c_str())); elog(); } const auto& user = usersList[userName]; std::string priv = user.get()->userPrivilege(); std::vector groupNames = user.get()->userGroups(); bool enabled = user.get()->userEnabled(); sdbusplus::message::object_path tempObjPath(usersObjPath); tempObjPath /= newUserName; std::string newUserObj(tempObjPath); // Special group 'ipmi' needs a way to identify user renamed, in order to // update encrypted password. It can't rely only on InterfacesRemoved & // InterfacesAdded. So first send out userRenamed signal. this->userRenamed(userName, newUserName); usersList.erase(userName); usersList.emplace(newUserName, std::make_unique( bus, newUserObj.c_str(), groupNames, priv, enabled, *this)); return; } void UserMgr::updateGroupsAndPriv(const std::string& userName, std::vector groupNames, const std::string& priv) { throwForInvalidPrivilege(priv); throwForInvalidGroups(groupNames); // All user management lock has to be based on /etc/shadow // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; throwForUserDoesNotExist(userName); const std::vector& oldGroupNames = usersList[userName].get()->userGroups(); std::vector groupDiff; // Note: already dealing with sorted group lists. std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(), groupNames.begin(), groupNames.end(), std::back_inserter(groupDiff)); if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") != groupDiff.end()) { throwForUserNameConstraints(userName, groupNames); throwForMaxGrpUserCount(groupNames); } std::string groups = getCSVFromVector(groupNames); bool sshRequested = removeStringFromCSV(groups, grpSsh); // treat privilege as a group - This is to avoid using different file to // store the same. if (!priv.empty()) { if (groups.size() != 0) { groups += ","; } groups += priv; } try { executeUserModify(userName.c_str(), groups.c_str(), sshRequested); } catch (const InternalFailure& e) { log("Unable to modify user privilege / groups"); elog(); } log("User groups / privilege updated successfully", entry("USER_NAME=%s", userName.c_str())); std::sort(groupNames.begin(), groupNames.end()); usersList[userName]->setUserGroups(groupNames); usersList[userName]->setUserPrivilege(priv); return; } uint8_t UserMgr::minPasswordLength(uint8_t value) { if (value == AccountPolicyIface::minPasswordLength()) { return value; } if (value < minPasswdLength) { log(("Attempting to set minPasswordLength to less than " + std::to_string(minPasswdLength)) .c_str(), entry("SIZE=%d", value)); elog( Argument::ARGUMENT_NAME("minPasswordLength"), Argument::ARGUMENT_VALUE(std::to_string(value).c_str())); } if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp, std::to_string(value)) != success) { log("Unable to set minPasswordLength"); elog(); } return AccountPolicyIface::minPasswordLength(value); } uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value) { if (value == AccountPolicyIface::rememberOldPasswordTimes()) { return value; } if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount, std::to_string(value)) != success) { log("Unable to set rememberOldPasswordTimes"); elog(); } return AccountPolicyIface::rememberOldPasswordTimes(value); } uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value) { if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout()) { return value; } if (setPamModuleArgValue(pamTally2, maxFailedAttempt, std::to_string(value)) != success) { log("Unable to set maxLoginAttemptBeforeLockout"); elog(); } return AccountPolicyIface::maxLoginAttemptBeforeLockout(value); } uint32_t UserMgr::accountUnlockTimeout(uint32_t value) { if (value == AccountPolicyIface::accountUnlockTimeout()) { return value; } if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) != success) { log("Unable to set accountUnlockTimeout"); elog(); } return AccountPolicyIface::accountUnlockTimeout(value); } int UserMgr::getPamModuleArgValue(const std::string& moduleName, const std::string& argName, std::string& argValue) { std::string fileName; if (moduleName == pamTally2) { fileName = pamAuthConfigFile; } else { fileName = pamPasswdConfigFile; } std::ifstream fileToRead(fileName, std::ios::in); if (!fileToRead.is_open()) { log("Failed to open pam configuration file", entry("FILE_NAME=%s", fileName.c_str())); return failure; } std::string line; auto argSearch = argName + "="; size_t startPos = 0; size_t endPos = 0; while (getline(fileToRead, line)) { // skip comments section starting with # if ((startPos = line.find('#')) != std::string::npos) { if (startPos == 0) { continue; } // skip comments after meaningful section and process those line = line.substr(0, startPos); } if (line.find(moduleName) != std::string::npos) { if ((startPos = line.find(argSearch)) != std::string::npos) { if ((endPos = line.find(' ', startPos)) == std::string::npos) { endPos = line.size(); } startPos += argSearch.size(); argValue = line.substr(startPos, endPos - startPos); return success; } } } return failure; } int UserMgr::setPamModuleArgValue(const std::string& moduleName, const std::string& argName, const std::string& argValue) { std::string fileName; if (moduleName == pamTally2) { fileName = pamAuthConfigFile; } else { fileName = pamPasswdConfigFile; } std::string tmpFileName = fileName + "_tmp"; std::ifstream fileToRead(fileName, std::ios::in); std::ofstream fileToWrite(tmpFileName, std::ios::out); if (!fileToRead.is_open() || !fileToWrite.is_open()) { log("Failed to open pam configuration /tmp file", entry("FILE_NAME=%s", fileName.c_str())); return failure; } std::string line; auto argSearch = argName + "="; size_t startPos = 0; size_t endPos = 0; bool found = false; while (getline(fileToRead, line)) { // skip comments section starting with # if ((startPos = line.find('#')) != std::string::npos) { if (startPos == 0) { fileToWrite << line << std::endl; continue; } // skip comments after meaningful section and process those line = line.substr(0, startPos); } if (line.find(moduleName) != std::string::npos) { if ((startPos = line.find(argSearch)) != std::string::npos) { if ((endPos = line.find(' ', startPos)) == std::string::npos) { endPos = line.size(); } startPos += argSearch.size(); fileToWrite << line.substr(0, startPos) << argValue << line.substr(endPos, line.size() - endPos) << std::endl; found = true; continue; } } fileToWrite << line << std::endl; } fileToWrite.close(); fileToRead.close(); if (found) { if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0) { return success; } } return failure; } void UserMgr::userEnable(const std::string& userName, bool enabled) { // All user management lock has to be based on /etc/shadow // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; throwForUserDoesNotExist(userName); try { executeUserModifyUserEnable(userName.c_str(), enabled); } catch (const InternalFailure& e) { log("Unable to modify user enabled state"); elog(); } log("User enabled/disabled state updated successfully", entry("USER_NAME=%s", userName.c_str()), entry("ENABLED=%d", enabled)); usersList[userName]->setUserEnabled(enabled); return; } /** * pam_tally2 app will provide the user failure count and failure status * in second line of output with words position [0] - user name, * [1] - failure count, [2] - latest failure date, [3] - latest failure time * [4] - failure app **/ static constexpr size_t t2FailCntIdx = 1; static constexpr size_t t2FailDateIdx = 2; static constexpr size_t t2FailTimeIdx = 3; static constexpr size_t t2OutputIndex = 1; bool UserMgr::userLockedForFailedAttempt(const std::string& userName) { // All user management lock has to be based on /etc/shadow // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0) { return false; } std::vector output; try { output = getFailedAttempt(userName.c_str()); } catch (const InternalFailure& e) { log("Unable to read login failure counter"); elog(); } std::vector splitWords; boost::algorithm::split(splitWords, output[t2OutputIndex], boost::algorithm::is_any_of("\t "), boost::token_compress_on); uint16_t failAttempts = 0; try { unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr); if (tmp > std::numeric_limits::max()) { throw std::out_of_range("Out of range"); } failAttempts = static_cast(tmp); } catch (const std::exception& e) { log("Exception for userLockedForFailedAttempt", entry("WHAT=%s", e.what())); elog(); } if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout()) { return false; } // When failedAttempts is not 0, Latest failure date/time should be // available if (splitWords.size() < 4) { log("Unable to read latest failure date/time"); elog(); } const std::string failDateTime = splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx]; // NOTE: Cannot use std::get_time() here as the implementation of %y in // libstdc++ does not match POSIX strptime() before gcc 12.1.0 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888 std::tm tmStruct = {}; if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct)) { log("Failed to parse latest failure date/time"); elog(); } time_t failTimestamp = std::mktime(&tmStruct); if (failTimestamp + static_cast(AccountPolicyIface::accountUnlockTimeout()) <= std::time(NULL)) { return false; } return true; } bool UserMgr::userLockedForFailedAttempt(const std::string& userName, const bool& value) { // All user management lock has to be based on /etc/shadow // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; if (value == true) { return userLockedForFailedAttempt(userName); } try { executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r"); } catch (const InternalFailure& e) { log("Unable to reset login failure counter"); elog(); } return userLockedForFailedAttempt(userName); } bool UserMgr::userPasswordExpired(const std::string& userName) { // All user management lock has to be based on /etc/shadow // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; struct spwd spwd {}; struct spwd* spwdPtr = nullptr; auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); if (buflen < -1) { // Use a default size if there is no hard limit suggested by sysconf() buflen = 1024; } std::vector buffer(buflen); auto status = getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr); // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd. // If no matching password record was found, these functions return 0 // and store NULL in *spwdPtr if ((status == 0) && (&spwd == spwdPtr)) { // Determine password validity per "chage" docs, where: // spwd.sp_lstchg == 0 means password is expired, and // spwd.sp_max == -1 means the password does not expire. constexpr long secondsPerDay = 60 * 60 * 24; long today = static_cast(time(NULL)) / secondsPerDay; if ((spwd.sp_lstchg == 0) || ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today))) { return true; } } else { // User entry is missing in /etc/shadow, indicating no SHA password. // Treat this as new user without password entry in /etc/shadow // TODO: Add property to indicate user password was not set yet // https://github.com/openbmc/phosphor-user-manager/issues/8 return false; } return false; } UserSSHLists UserMgr::getUserAndSshGrpList() { // All user management lock has to be based on /etc/shadow // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; std::vector userList; std::vector sshUsersList; struct passwd pw, *pwp = nullptr; std::array buffer{}; phosphor::user::File passwd(passwdFileName, "r"); if ((passwd)() == NULL) { log("Error opening the passwd file"); elog(); } while (true) { auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(), &pwp); if ((r != 0) || (pwp == NULL)) { // Any error, break the loop. break; } #ifdef ENABLE_ROOT_USER_MGMT // Add all users whose UID >= 1000 and < 65534 // and special UID 0. if ((pwp->pw_uid == 0) || ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))) #else // Add all users whose UID >=1000 and < 65534 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)) #endif { std::string userName(pwp->pw_name); userList.emplace_back(userName); // ssh doesn't have separate group. Check login shell entry to // get all users list which are member of ssh group. std::string loginShell(pwp->pw_shell); if (loginShell == "/bin/sh") { sshUsersList.emplace_back(userName); } } } endpwent(); return std::make_pair(std::move(userList), std::move(sshUsersList)); } size_t UserMgr::getIpmiUsersCount() { std::vector userList = getUsersInGroup("ipmi"); return userList.size(); } size_t UserMgr::getNonIpmiUsersCount() { std::vector ipmiUsers = getUsersInGroup("ipmi"); return usersList.size() - ipmiUsers.size(); } bool UserMgr::isUserEnabled(const std::string& userName) { // All user management lock has to be based on /etc/shadow // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; std::array buffer{}; struct spwd spwd; struct spwd* resultPtr = nullptr; int status = getspnam_r(userName.c_str(), &spwd, buffer.data(), buffer.max_size(), &resultPtr); if (!status && (&spwd == resultPtr)) { if (resultPtr->sp_expire >= 0) { return false; // user locked out } return true; } return false; // assume user is disabled for any error. } std::vector UserMgr::getUsersInGroup(const std::string& groupName) { std::vector usersInGroup; // Should be more than enough to get the pwd structure. std::array buffer{}; struct group grp; struct group* resultPtr = nullptr; int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(), buffer.max_size(), &resultPtr); if (!status && (&grp == resultPtr)) { for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem)) { usersInGroup.emplace_back(*(grp.gr_mem)); } } else { log("Group not found", entry("GROUP=%s", groupName.c_str())); // Don't throw error, just return empty userList - fallback } return usersInGroup; } DbusUserObj UserMgr::getPrivilegeMapperObject(void) { DbusUserObj objects; try { std::string basePath = "/xyz/openbmc_project/user/ldap/openldap"; std::string interface = "xyz.openbmc_project.User.Ldap.Config"; auto ldapMgmtService = getServiceName(std::move(basePath), std::move(interface)); auto method = bus.new_method_call( ldapMgmtService.c_str(), ldapMgrObjBasePath, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); auto reply = bus.call(method); reply.read(objects); } catch (const InternalFailure& e) { log("Unable to get the User Service", entry("WHAT=%s", e.what())); throw; } catch (const sdbusplus::exception_t& e) { log( "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"), entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what())); throw; } return objects; } std::string UserMgr::getLdapGroupName(const std::string& userName) { struct passwd pwd {}; struct passwd* pwdPtr = nullptr; auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); if (buflen < -1) { // Use a default size if there is no hard limit suggested by sysconf() buflen = 1024; } std::vector buffer(buflen); gid_t gid = 0; auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr); // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd. // If no matching password record was found, these functions return 0 // and store NULL in *pwdPtr if (!status && (&pwd == pwdPtr)) { gid = pwd.pw_gid; } else { log("User does not exist", entry("USER_NAME=%s", userName.c_str())); elog(); } struct group* groups = nullptr; std::string ldapGroupName; while ((groups = getgrent()) != NULL) { if (groups->gr_gid == gid) { ldapGroupName = groups->gr_name; break; } } // Call endgrent() to close the group database. endgrent(); return ldapGroupName; } std::string UserMgr::getServiceName(std::string&& path, std::string&& intf) { auto mapperCall = bus.new_method_call(objMapperService, objMapperPath, objMapperInterface, "GetObject"); mapperCall.append(std::move(path)); mapperCall.append(std::vector({std::move(intf)})); auto mapperResponseMsg = bus.call(mapperCall); if (mapperResponseMsg.is_method_error()) { log("Error in mapper call"); elog(); } std::map> mapperResponse; mapperResponseMsg.read(mapperResponse); if (mapperResponse.begin() == mapperResponse.end()) { log("Invalid response from mapper"); elog(); } return mapperResponse.begin()->first; } UserInfoMap UserMgr::getUserInfo(std::string userName) { UserInfoMap userInfo; // Check whether the given user is local user or not. if (isUserExist(userName)) { const auto& user = usersList[userName]; userInfo.emplace("UserPrivilege", user.get()->userPrivilege()); userInfo.emplace("UserGroups", user.get()->userGroups()); userInfo.emplace("UserEnabled", user.get()->userEnabled()); userInfo.emplace("UserLockedForFailedAttempt", user.get()->userLockedForFailedAttempt()); userInfo.emplace("UserPasswordExpired", user.get()->userPasswordExpired()); userInfo.emplace("RemoteUser", false); } else { std::string ldapGroupName = getLdapGroupName(userName); if (ldapGroupName.empty()) { log("Unable to get group name", entry("USER_NAME=%s", userName.c_str())); elog(); } DbusUserObj objects = getPrivilegeMapperObject(); std::string ldapConfigPath; std::string userPrivilege; try { for (const auto& obj : objects) { for (const auto& interface : obj.second) { if ((interface.first == "xyz.openbmc_project.Object.Enable")) { for (const auto& property : interface.second) { auto value = std::get(property.second); if ((property.first == "Enabled") && (value == true)) { ldapConfigPath = obj.first; break; } } } } if (!ldapConfigPath.empty()) { break; } } if (ldapConfigPath.empty()) { return userInfo; } for (const auto& obj : objects) { for (const auto& interface : obj.second) { if ((interface.first == "xyz.openbmc_project.User.PrivilegeMapperEntry") && (obj.first.str.find(ldapConfigPath) != std::string::npos)) { std::string privilege; std::string groupName; for (const auto& property : interface.second) { auto value = std::get(property.second); if (property.first == "GroupName") { groupName = value; } else if (property.first == "Privilege") { privilege = value; } } if (groupName == ldapGroupName) { userPrivilege = privilege; break; } } } if (!userPrivilege.empty()) { break; } } if (userPrivilege.empty()) { log("LDAP group privilege mapping does not exist"); } userInfo.emplace("UserPrivilege", userPrivilege); } catch (const std::bad_variant_access& e) { log("Error while accessing variant", entry("WHAT=%s", e.what())); elog(); } userInfo.emplace("RemoteUser", true); } return userInfo; } void UserMgr::initializeAccountPolicy() { std::string valueStr; auto value = minPasswdLength; unsigned long tmp = 0; if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) != success) { AccountPolicyIface::minPasswordLength(minPasswdLength); } else { try { tmp = std::stoul(valueStr, nullptr); if (tmp > std::numeric_limits::max()) { throw std::out_of_range("Out of range"); } value = static_cast(tmp); } catch (const std::exception& e) { log("Exception for MinPasswordLength", entry("WHAT=%s", e.what())); throw; } AccountPolicyIface::minPasswordLength(value); } valueStr.clear(); if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) != success) { AccountPolicyIface::rememberOldPasswordTimes(0); } else { value = 0; try { tmp = std::stoul(valueStr, nullptr); if (tmp > std::numeric_limits::max()) { throw std::out_of_range("Out of range"); } value = static_cast(tmp); } catch (const std::exception& e) { log("Exception for RememberOldPasswordTimes", entry("WHAT=%s", e.what())); throw; } AccountPolicyIface::rememberOldPasswordTimes(value); } valueStr.clear(); if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success) { AccountPolicyIface::maxLoginAttemptBeforeLockout(0); } else { uint16_t value16 = 0; try { tmp = std::stoul(valueStr, nullptr); if (tmp > std::numeric_limits::max()) { throw std::out_of_range("Out of range"); } value16 = static_cast(tmp); } catch (const std::exception& e) { log("Exception for MaxLoginAttemptBeforLockout", entry("WHAT=%s", e.what())); throw; } AccountPolicyIface::maxLoginAttemptBeforeLockout(value16); } valueStr.clear(); if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success) { AccountPolicyIface::accountUnlockTimeout(0); } else { uint32_t value32 = 0; try { tmp = std::stoul(valueStr, nullptr); if (tmp > std::numeric_limits::max()) { throw std::out_of_range("Out of range"); } value32 = static_cast(tmp); } catch (const std::exception& e) { log("Exception for AccountUnlockTimeout", entry("WHAT=%s", e.what())); throw; } AccountPolicyIface::accountUnlockTimeout(value32); } } void UserMgr::initUserObjects(void) { // All user management lock has to be based on /etc/shadow // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; std::vector userNameList; std::vector sshGrpUsersList; UserSSHLists userSSHLists = getUserAndSshGrpList(); userNameList = std::move(userSSHLists.first); sshGrpUsersList = std::move(userSSHLists.second); if (!userNameList.empty()) { std::map> groupLists; for (auto& grp : groupsMgr) { if (grp == grpSsh) { groupLists.emplace(grp, sshGrpUsersList); } else { std::vector grpUsersList = getUsersInGroup(grp); groupLists.emplace(grp, grpUsersList); } } for (auto& grp : privMgr) { std::vector grpUsersList = getUsersInGroup(grp); groupLists.emplace(grp, grpUsersList); } for (auto& user : userNameList) { std::vector userGroups; std::string userPriv; for (const auto& grp : groupLists) { std::vector tempGrp = grp.second; if (std::find(tempGrp.begin(), tempGrp.end(), user) != tempGrp.end()) { if (std::find(privMgr.begin(), privMgr.end(), grp.first) != privMgr.end()) { userPriv = grp.first; } else { userGroups.emplace_back(grp.first); } } } // Add user objects to the Users path. sdbusplus::message::object_path tempObjPath(usersObjPath); tempObjPath /= user; std::string objPath(tempObjPath); std::sort(userGroups.begin(), userGroups.end()); usersList.emplace(user, std::make_unique( bus, objPath.c_str(), userGroups, userPriv, isUserEnabled(user), *this)); } } } UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) : Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path), pamPasswdConfigFile(defaultPamPasswdConfigFile), pamAuthConfigFile(defaultPamAuthConfigFile) { UserMgrIface::allPrivileges(privMgr); std::sort(groupsMgr.begin(), groupsMgr.end()); UserMgrIface::allGroups(groupsMgr); initializeAccountPolicy(); initUserObjects(); // emit the signal this->emit_object_added(); } void UserMgr::executeUserAdd(const char* userName, const char* groups, bool sshRequested, bool enabled) { // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on // 1970-01-01, that's an implementation-defined behavior executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e", (enabled ? "" : "1970-01-01")); } void UserMgr::executeUserDelete(const char* userName) { executeCmd("/usr/sbin/userdel", userName, "-r"); } void UserMgr::executeUserRename(const char* userName, const char* newUserName) { std::string newHomeDir = "/home/"; newHomeDir += newUserName; executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d", newHomeDir.c_str(), "-m"); } void UserMgr::executeUserModify(const char* userName, const char* newGroups, bool sshRequested) { executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s", (sshRequested ? "/bin/sh" : "/bin/nologin")); } void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled) { // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on // 1970-01-01, that's an implementation-defined behavior executeCmd("/usr/sbin/usermod", userName, "-e", (enabled ? "" : "1970-01-01")); } std::vector UserMgr::getFailedAttempt(const char* userName) { return executeCmd("/usr/sbin/pam_tally2", "-u", userName); } void UserMgr::createGroup(std::string /*groupName*/) { log("Not implemented yet"); elog(); } void UserMgr::deleteGroup(std::string /*groupName*/) { log("Not implemented yet"); elog(); } } // namespace user } // namespace phosphor