xref: /openbmc/phosphor-user-manager/user_mgr.cpp (revision 93804eba13ade6aebfa38eaac5349b560b5cae33)
19f630d9eSRichard Marian Thomaiyar /*
29f630d9eSRichard Marian Thomaiyar // Copyright (c) 2018 Intel Corporation
39f630d9eSRichard Marian Thomaiyar //
49f630d9eSRichard Marian Thomaiyar // Licensed under the Apache License, Version 2.0 (the "License");
59f630d9eSRichard Marian Thomaiyar // you may not use this file except in compliance with the License.
69f630d9eSRichard Marian Thomaiyar // You may obtain a copy of the License at
79f630d9eSRichard Marian Thomaiyar //
89f630d9eSRichard Marian Thomaiyar //      http://www.apache.org/licenses/LICENSE-2.0
99f630d9eSRichard Marian Thomaiyar //
109f630d9eSRichard Marian Thomaiyar // Unless required by applicable law or agreed to in writing, software
119f630d9eSRichard Marian Thomaiyar // distributed under the License is distributed on an "AS IS" BASIS,
129f630d9eSRichard Marian Thomaiyar // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139f630d9eSRichard Marian Thomaiyar // See the License for the specific language governing permissions and
149f630d9eSRichard Marian Thomaiyar // limitations under the License.
159f630d9eSRichard Marian Thomaiyar */
169f630d9eSRichard Marian Thomaiyar 
179638afb9SPatrick Williams #include "config.h"
189638afb9SPatrick Williams 
199638afb9SPatrick Williams #include "user_mgr.hpp"
209638afb9SPatrick Williams 
219638afb9SPatrick Williams #include "file.hpp"
229638afb9SPatrick Williams #include "shadowlock.hpp"
239638afb9SPatrick Williams #include "users.hpp"
249638afb9SPatrick Williams 
259638afb9SPatrick Williams #include <grp.h>
269638afb9SPatrick Williams #include <pwd.h>
279f630d9eSRichard Marian Thomaiyar #include <shadow.h>
289f630d9eSRichard Marian Thomaiyar #include <sys/types.h>
299f630d9eSRichard Marian Thomaiyar #include <sys/wait.h>
303ab6cc28SJoseph Reynolds #include <time.h>
319638afb9SPatrick Williams #include <unistd.h>
329638afb9SPatrick Williams 
339638afb9SPatrick Williams #include <boost/algorithm/string/split.hpp>
349638afb9SPatrick Williams #include <phosphor-logging/elog-errors.hpp>
359638afb9SPatrick Williams #include <phosphor-logging/elog.hpp>
3611ec666bSJiaqing Zhao #include <phosphor-logging/lg2.hpp>
379f630d9eSRichard Marian Thomaiyar #include <xyz/openbmc_project/Common/error.hpp>
389f630d9eSRichard Marian Thomaiyar #include <xyz/openbmc_project/User/Common/error.hpp>
399638afb9SPatrick Williams 
409638afb9SPatrick Williams #include <algorithm>
41da401fe5SNan Zhou #include <array>
42fba4bb17SJiaqing Zhao #include <ctime>
43*93804ebaSAbhilash Raju #include <filesystem>
449638afb9SPatrick Williams #include <fstream>
459638afb9SPatrick Williams #include <numeric>
469638afb9SPatrick Williams #include <regex>
47e47c09d3SNan Zhou #include <span>
48e47c09d3SNan Zhou #include <string>
49e47c09d3SNan Zhou #include <string_view>
50e47c09d3SNan Zhou #include <vector>
519f630d9eSRichard Marian Thomaiyar namespace phosphor
529f630d9eSRichard Marian Thomaiyar {
539f630d9eSRichard Marian Thomaiyar namespace user
549f630d9eSRichard Marian Thomaiyar {
559f630d9eSRichard Marian Thomaiyar 
569f630d9eSRichard Marian Thomaiyar static constexpr const char* passwdFileName = "/etc/passwd";
579f630d9eSRichard Marian Thomaiyar static constexpr size_t ipmiMaxUserNameLen = 16;
5827d56764SMalik Akbar Hashemi Rafsanjani static constexpr size_t systemMaxUserNameLen = 100;
599f630d9eSRichard Marian Thomaiyar static constexpr const char* grpSsh = "ssh";
609164fd9bSRichard Marian Thomaiyar static constexpr int success = 0;
619164fd9bSRichard Marian Thomaiyar static constexpr int failure = -1;
629164fd9bSRichard Marian Thomaiyar 
639164fd9bSRichard Marian Thomaiyar // pam modules related
649164fd9bSRichard Marian Thomaiyar static constexpr const char* minPasswdLenProp = "minlen";
659164fd9bSRichard Marian Thomaiyar static constexpr const char* remOldPasswdCount = "remember";
669164fd9bSRichard Marian Thomaiyar static constexpr const char* maxFailedAttempt = "deny";
679164fd9bSRichard Marian Thomaiyar static constexpr const char* unlockTimeout = "unlock_time";
682d042d14SJason M. Bills static constexpr const char* defaultFaillockConfigFile =
692d042d14SJason M. Bills     "/etc/security/faillock.conf";
703b280ec7SJason M. Bills static constexpr const char* defaultPWHistoryConfigFile =
713b280ec7SJason M. Bills     "/etc/security/pwhistory.conf";
722d042d14SJason M. Bills static constexpr const char* defaultPWQualityConfigFile =
732d042d14SJason M. Bills     "/etc/security/pwquality.conf";
749f630d9eSRichard Marian Thomaiyar 
75aeaf9413SRatan Gupta // Object Manager related
76aeaf9413SRatan Gupta static constexpr const char* ldapMgrObjBasePath =
77aeaf9413SRatan Gupta     "/xyz/openbmc_project/user/ldap";
78aeaf9413SRatan Gupta 
79aeaf9413SRatan Gupta // Object Mapper related
80aeaf9413SRatan Gupta static constexpr const char* objMapperService =
81aeaf9413SRatan Gupta     "xyz.openbmc_project.ObjectMapper";
82aeaf9413SRatan Gupta static constexpr const char* objMapperPath =
83aeaf9413SRatan Gupta     "/xyz/openbmc_project/object_mapper";
84aeaf9413SRatan Gupta static constexpr const char* objMapperInterface =
85aeaf9413SRatan Gupta     "xyz.openbmc_project.ObjectMapper";
86aeaf9413SRatan Gupta 
879f630d9eSRichard Marian Thomaiyar using namespace phosphor::logging;
889f630d9eSRichard Marian Thomaiyar using InsufficientPermission =
899f630d9eSRichard Marian Thomaiyar     sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
909f630d9eSRichard Marian Thomaiyar using InternalFailure =
919f630d9eSRichard Marian Thomaiyar     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
929f630d9eSRichard Marian Thomaiyar using InvalidArgument =
939f630d9eSRichard Marian Thomaiyar     sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
949f630d9eSRichard Marian Thomaiyar using UserNameExists =
959f630d9eSRichard Marian Thomaiyar     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
969f630d9eSRichard Marian Thomaiyar using UserNameDoesNotExist =
979f630d9eSRichard Marian Thomaiyar     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
989f630d9eSRichard Marian Thomaiyar using UserNameGroupFail =
999f630d9eSRichard Marian Thomaiyar     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
1009f630d9eSRichard Marian Thomaiyar using NoResource =
1019f630d9eSRichard Marian Thomaiyar     sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
1029f630d9eSRichard Marian Thomaiyar using Argument = xyz::openbmc_project::Common::InvalidArgument;
103da401fe5SNan Zhou using GroupNameExists =
104da401fe5SNan Zhou     sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameExists;
105da401fe5SNan Zhou using GroupNameDoesNotExists =
106da401fe5SNan Zhou     sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameDoesNotExist;
107da401fe5SNan Zhou 
108da401fe5SNan Zhou namespace
109da401fe5SNan Zhou {
110*93804ebaSAbhilash Raju constexpr auto mfaConfPath = "/var/lib/usr_mgr.conf";
111da401fe5SNan Zhou // The hardcoded groups in OpenBMC projects
11216c2b681SPatrick Williams constexpr std::array<const char*, 4> predefinedGroups = {
11316c2b681SPatrick Williams     "redfish", "ipmi", "ssh", "hostconsole"};
114da401fe5SNan Zhou 
115da401fe5SNan Zhou // These prefixes are for Dynamic Redfish authorization. See
116da401fe5SNan Zhou // https://github.com/openbmc/docs/blob/master/designs/redfish-authorization.md
117da401fe5SNan Zhou 
118da401fe5SNan Zhou // Base role and base privileges are added by Redfish implementation (e.g.,
119da401fe5SNan Zhou // BMCWeb) at compile time
120da401fe5SNan Zhou constexpr std::array<const char*, 4> allowedGroupPrefix = {
121da401fe5SNan Zhou     "openbmc_rfr_",  // OpenBMC Redfish Base Role
122da401fe5SNan Zhou     "openbmc_rfp_",  // OpenBMC Redfish Base Privileges
123da401fe5SNan Zhou     "openbmc_orfr_", // OpenBMC Redfish OEM Role
124da401fe5SNan Zhou     "openbmc_orfp_", // OpenBMC Redfish OEM Privileges
125da401fe5SNan Zhou };
126da401fe5SNan Zhou 
checkAndThrowsForGroupChangeAllowed(const std::string & groupName)127da401fe5SNan Zhou void checkAndThrowsForGroupChangeAllowed(const std::string& groupName)
128da401fe5SNan Zhou {
129da401fe5SNan Zhou     bool allowed = false;
130da401fe5SNan Zhou     for (std::string_view prefix : allowedGroupPrefix)
131da401fe5SNan Zhou     {
132da401fe5SNan Zhou         if (groupName.starts_with(prefix))
133da401fe5SNan Zhou         {
134da401fe5SNan Zhou             allowed = true;
135da401fe5SNan Zhou             break;
136da401fe5SNan Zhou         }
137da401fe5SNan Zhou     }
138da401fe5SNan Zhou     if (!allowed)
139da401fe5SNan Zhou     {
14011ec666bSJiaqing Zhao         lg2::error("Group name '{GROUP}' is not in the allowed list", "GROUP",
14111ec666bSJiaqing Zhao                    groupName);
142da401fe5SNan Zhou         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
143da401fe5SNan Zhou                               Argument::ARGUMENT_VALUE(groupName.c_str()));
144da401fe5SNan Zhou     }
145da401fe5SNan Zhou }
146da401fe5SNan Zhou 
147da401fe5SNan Zhou } // namespace
1489f630d9eSRichard Marian Thomaiyar 
getCSVFromVector(std::span<const std::string> vec)149e47c09d3SNan Zhou std::string getCSVFromVector(std::span<const std::string> vec)
1509f630d9eSRichard Marian Thomaiyar {
151e47c09d3SNan Zhou     if (vec.empty())
1529f630d9eSRichard Marian Thomaiyar     {
1539f630d9eSRichard Marian Thomaiyar         return "";
1549f630d9eSRichard Marian Thomaiyar     }
155e47c09d3SNan Zhou     return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
156e47c09d3SNan Zhou                            [](std::string&& val, std::string_view element) {
157e47c09d3SNan Zhou                                val += ',';
158e47c09d3SNan Zhou                                val += element;
159e47c09d3SNan Zhou                                return val;
160e47c09d3SNan Zhou                            });
1619f630d9eSRichard Marian Thomaiyar }
1629f630d9eSRichard Marian Thomaiyar 
removeStringFromCSV(std::string & csvStr,const std::string & delStr)163332fb9dcSNan Zhou bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
1649f630d9eSRichard Marian Thomaiyar {
1659f630d9eSRichard Marian Thomaiyar     std::string::size_type delStrPos = csvStr.find(delStr);
1669f630d9eSRichard Marian Thomaiyar     if (delStrPos != std::string::npos)
1679f630d9eSRichard Marian Thomaiyar     {
1689f630d9eSRichard Marian Thomaiyar         // need to also delete the comma char
1699f630d9eSRichard Marian Thomaiyar         if (delStrPos == 0)
1709f630d9eSRichard Marian Thomaiyar         {
1719f630d9eSRichard Marian Thomaiyar             csvStr.erase(delStrPos, delStr.size() + 1);
1729f630d9eSRichard Marian Thomaiyar         }
1739f630d9eSRichard Marian Thomaiyar         else
1749f630d9eSRichard Marian Thomaiyar         {
1759f630d9eSRichard Marian Thomaiyar             csvStr.erase(delStrPos - 1, delStr.size() + 1);
1769f630d9eSRichard Marian Thomaiyar         }
1779f630d9eSRichard Marian Thomaiyar         return true;
1789f630d9eSRichard Marian Thomaiyar     }
1799f630d9eSRichard Marian Thomaiyar     return false;
1809f630d9eSRichard Marian Thomaiyar }
1819f630d9eSRichard Marian Thomaiyar 
isUserExist(const std::string & userName)1829f630d9eSRichard Marian Thomaiyar bool UserMgr::isUserExist(const std::string& userName)
1839f630d9eSRichard Marian Thomaiyar {
1849f630d9eSRichard Marian Thomaiyar     if (userName.empty())
1859f630d9eSRichard Marian Thomaiyar     {
18611ec666bSJiaqing Zhao         lg2::error("User name is empty");
1879f630d9eSRichard Marian Thomaiyar         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
1889f630d9eSRichard Marian Thomaiyar                               Argument::ARGUMENT_VALUE("Null"));
1899f630d9eSRichard Marian Thomaiyar     }
1909f630d9eSRichard Marian Thomaiyar     if (usersList.find(userName) == usersList.end())
1919f630d9eSRichard Marian Thomaiyar     {
1929f630d9eSRichard Marian Thomaiyar         return false;
1939f630d9eSRichard Marian Thomaiyar     }
1949f630d9eSRichard Marian Thomaiyar     return true;
1959f630d9eSRichard Marian Thomaiyar }
1969f630d9eSRichard Marian Thomaiyar 
throwForUserDoesNotExist(const std::string & userName)1979f630d9eSRichard Marian Thomaiyar void UserMgr::throwForUserDoesNotExist(const std::string& userName)
1989f630d9eSRichard Marian Thomaiyar {
1998a11d998SNan Zhou     if (!isUserExist(userName))
2009f630d9eSRichard Marian Thomaiyar     {
20111ec666bSJiaqing Zhao         lg2::error("User '{USERNAME}' does not exist", "USERNAME", userName);
2029f630d9eSRichard Marian Thomaiyar         elog<UserNameDoesNotExist>();
2039f630d9eSRichard Marian Thomaiyar     }
2049f630d9eSRichard Marian Thomaiyar }
2059f630d9eSRichard Marian Thomaiyar 
checkAndThrowForDisallowedGroupCreation(const std::string & groupName)206da401fe5SNan Zhou void UserMgr::checkAndThrowForDisallowedGroupCreation(
207da401fe5SNan Zhou     const std::string& groupName)
208da401fe5SNan Zhou {
209da401fe5SNan Zhou     if (groupName.size() > maxSystemGroupNameLength ||
210da401fe5SNan Zhou         !std::regex_match(groupName.c_str(),
211d9adc73aSnichanghao.nch                           std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
212da401fe5SNan Zhou     {
21311ec666bSJiaqing Zhao         lg2::error("Invalid group name '{GROUP}'", "GROUP", groupName);
214da401fe5SNan Zhou         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
215da401fe5SNan Zhou                               Argument::ARGUMENT_VALUE(groupName.c_str()));
216da401fe5SNan Zhou     }
217da401fe5SNan Zhou     checkAndThrowsForGroupChangeAllowed(groupName);
218da401fe5SNan Zhou }
219da401fe5SNan Zhou 
throwForUserExists(const std::string & userName)2209f630d9eSRichard Marian Thomaiyar void UserMgr::throwForUserExists(const std::string& userName)
2219f630d9eSRichard Marian Thomaiyar {
2228a11d998SNan Zhou     if (isUserExist(userName))
2239f630d9eSRichard Marian Thomaiyar     {
22411ec666bSJiaqing Zhao         lg2::error("User '{USERNAME}' already exists", "USERNAME", userName);
2259f630d9eSRichard Marian Thomaiyar         elog<UserNameExists>();
2269f630d9eSRichard Marian Thomaiyar     }
2279f630d9eSRichard Marian Thomaiyar }
2289f630d9eSRichard Marian Thomaiyar 
throwForUserNameConstraints(const std::string & userName,const std::vector<std::string> & groupNames)2299f630d9eSRichard Marian Thomaiyar void UserMgr::throwForUserNameConstraints(
2309f630d9eSRichard Marian Thomaiyar     const std::string& userName, const std::vector<std::string>& groupNames)
2319f630d9eSRichard Marian Thomaiyar {
2329f630d9eSRichard Marian Thomaiyar     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
2339f630d9eSRichard Marian Thomaiyar         groupNames.end())
2349f630d9eSRichard Marian Thomaiyar     {
2359f630d9eSRichard Marian Thomaiyar         if (userName.length() > ipmiMaxUserNameLen)
2369f630d9eSRichard Marian Thomaiyar         {
23711ec666bSJiaqing Zhao             lg2::error("User '{USERNAME}' exceeds IPMI username length limit "
23811ec666bSJiaqing Zhao                        "({LENGTH} > {LIMIT})",
23911ec666bSJiaqing Zhao                        "USERNAME", userName, "LENGTH", userName.length(),
24011ec666bSJiaqing Zhao                        "LIMIT", ipmiMaxUserNameLen);
2419f630d9eSRichard Marian Thomaiyar             elog<UserNameGroupFail>(
2429f630d9eSRichard Marian Thomaiyar                 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
2439f630d9eSRichard Marian Thomaiyar                     "IPMI length"));
2449f630d9eSRichard Marian Thomaiyar         }
2459f630d9eSRichard Marian Thomaiyar     }
2469f630d9eSRichard Marian Thomaiyar     if (userName.length() > systemMaxUserNameLen)
2479f630d9eSRichard Marian Thomaiyar     {
24811ec666bSJiaqing Zhao         lg2::error("User '{USERNAME}' exceeds system username length limit "
24911ec666bSJiaqing Zhao                    "({LENGTH} > {LIMIT})",
25011ec666bSJiaqing Zhao                    "USERNAME", userName, "LENGTH", userName.length(), "LIMIT",
25111ec666bSJiaqing Zhao                    systemMaxUserNameLen);
2529f630d9eSRichard Marian Thomaiyar         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
2539f630d9eSRichard Marian Thomaiyar                               Argument::ARGUMENT_VALUE("Invalid length"));
2549f630d9eSRichard Marian Thomaiyar     }
2559f630d9eSRichard Marian Thomaiyar     if (!std::regex_match(userName.c_str(),
256d9adc73aSnichanghao.nch                           std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
2579f630d9eSRichard Marian Thomaiyar     {
25811ec666bSJiaqing Zhao         lg2::error("Invalid username '{USERNAME}'", "USERNAME", userName);
2599f630d9eSRichard Marian Thomaiyar         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
2609f630d9eSRichard Marian Thomaiyar                               Argument::ARGUMENT_VALUE("Invalid data"));
2619f630d9eSRichard Marian Thomaiyar     }
2629f630d9eSRichard Marian Thomaiyar }
2639f630d9eSRichard Marian Thomaiyar 
throwForMaxGrpUserCount(const std::vector<std::string> & groupNames)26488a82dbcSPatrick Williams void UserMgr::throwForMaxGrpUserCount(
26588a82dbcSPatrick Williams     const std::vector<std::string>& groupNames)
2669f630d9eSRichard Marian Thomaiyar {
2679f630d9eSRichard Marian Thomaiyar     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
2689f630d9eSRichard Marian Thomaiyar         groupNames.end())
2699f630d9eSRichard Marian Thomaiyar     {
2709f630d9eSRichard Marian Thomaiyar         if (getIpmiUsersCount() >= ipmiMaxUsers)
2719f630d9eSRichard Marian Thomaiyar         {
27211ec666bSJiaqing Zhao             lg2::error("IPMI user limit reached");
2739f630d9eSRichard Marian Thomaiyar             elog<NoResource>(
2749f630d9eSRichard Marian Thomaiyar                 xyz::openbmc_project::User::Common::NoResource::REASON(
27511ec666bSJiaqing Zhao                     "IPMI user limit reached"));
2769f630d9eSRichard Marian Thomaiyar         }
2779f630d9eSRichard Marian Thomaiyar     }
2789f630d9eSRichard Marian Thomaiyar     else
2799f630d9eSRichard Marian Thomaiyar     {
2809f630d9eSRichard Marian Thomaiyar         if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
2819f630d9eSRichard Marian Thomaiyar                                         (maxSystemUsers - ipmiMaxUsers))
2829f630d9eSRichard Marian Thomaiyar         {
28311ec666bSJiaqing Zhao             lg2::error("Non-ipmi User limit reached");
2849f630d9eSRichard Marian Thomaiyar             elog<NoResource>(
2859f630d9eSRichard Marian Thomaiyar                 xyz::openbmc_project::User::Common::NoResource::REASON(
28611ec666bSJiaqing Zhao                     "Non-ipmi user limit reached"));
2879f630d9eSRichard Marian Thomaiyar         }
2889f630d9eSRichard Marian Thomaiyar     }
2899f630d9eSRichard Marian Thomaiyar     return;
2909f630d9eSRichard Marian Thomaiyar }
2919f630d9eSRichard Marian Thomaiyar 
throwForInvalidPrivilege(const std::string & priv)2929f630d9eSRichard Marian Thomaiyar void UserMgr::throwForInvalidPrivilege(const std::string& priv)
2939f630d9eSRichard Marian Thomaiyar {
2949f630d9eSRichard Marian Thomaiyar     if (!priv.empty() &&
2959f630d9eSRichard Marian Thomaiyar         (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
2969f630d9eSRichard Marian Thomaiyar     {
29711ec666bSJiaqing Zhao         lg2::error("Invalid privilege '{PRIVILEGE}'", "PRIVILEGE", priv);
2989f630d9eSRichard Marian Thomaiyar         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
2999f630d9eSRichard Marian Thomaiyar                               Argument::ARGUMENT_VALUE(priv.c_str()));
3009f630d9eSRichard Marian Thomaiyar     }
3019f630d9eSRichard Marian Thomaiyar }
3029f630d9eSRichard Marian Thomaiyar 
throwForInvalidGroups(const std::vector<std::string> & groupNames)3039f630d9eSRichard Marian Thomaiyar void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
3049f630d9eSRichard Marian Thomaiyar {
3059f630d9eSRichard Marian Thomaiyar     for (auto& group : groupNames)
3069f630d9eSRichard Marian Thomaiyar     {
3079f630d9eSRichard Marian Thomaiyar         if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
3089f630d9eSRichard Marian Thomaiyar             groupsMgr.end())
3099f630d9eSRichard Marian Thomaiyar         {
31011ec666bSJiaqing Zhao             lg2::error("Invalid Group Name '{GROUPNAME}'", "GROUPNAME", group);
3119f630d9eSRichard Marian Thomaiyar             elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
3129f630d9eSRichard Marian Thomaiyar                                   Argument::ARGUMENT_VALUE(group.c_str()));
3139f630d9eSRichard Marian Thomaiyar         }
3149f630d9eSRichard Marian Thomaiyar     }
3159f630d9eSRichard Marian Thomaiyar }
3169f630d9eSRichard Marian Thomaiyar 
readAllGroupsOnSystem()317da401fe5SNan Zhou std::vector<std::string> UserMgr::readAllGroupsOnSystem()
318da401fe5SNan Zhou {
319da401fe5SNan Zhou     std::vector<std::string> allGroups = {predefinedGroups.begin(),
320da401fe5SNan Zhou                                           predefinedGroups.end()};
321da401fe5SNan Zhou     // rewinds to the beginning of the group database
322da401fe5SNan Zhou     setgrent();
323da401fe5SNan Zhou     struct group* gr = getgrent();
324da401fe5SNan Zhou     while (gr != nullptr)
325da401fe5SNan Zhou     {
326da401fe5SNan Zhou         std::string group(gr->gr_name);
327da401fe5SNan Zhou         for (std::string_view prefix : allowedGroupPrefix)
328da401fe5SNan Zhou         {
329da401fe5SNan Zhou             if (group.starts_with(prefix))
330da401fe5SNan Zhou             {
331da401fe5SNan Zhou                 allGroups.push_back(gr->gr_name);
332da401fe5SNan Zhou             }
333da401fe5SNan Zhou         }
334da401fe5SNan Zhou         gr = getgrent();
335da401fe5SNan Zhou     }
336da401fe5SNan Zhou     // close the group database
337da401fe5SNan Zhou     endgrent();
338da401fe5SNan Zhou     return allGroups;
339da401fe5SNan Zhou }
340da401fe5SNan Zhou 
createUser(std::string userName,std::vector<std::string> groupNames,std::string priv,bool enabled)3419f630d9eSRichard Marian Thomaiyar void UserMgr::createUser(std::string userName,
3429f630d9eSRichard Marian Thomaiyar                          std::vector<std::string> groupNames, std::string priv,
3439f630d9eSRichard Marian Thomaiyar                          bool enabled)
3449f630d9eSRichard Marian Thomaiyar {
3459f630d9eSRichard Marian Thomaiyar     throwForInvalidPrivilege(priv);
3469f630d9eSRichard Marian Thomaiyar     throwForInvalidGroups(groupNames);
3479f630d9eSRichard Marian Thomaiyar     // All user management lock has to be based on /etc/shadow
348a260f187SAndrew Geissler     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
3499f630d9eSRichard Marian Thomaiyar     throwForUserExists(userName);
3509f630d9eSRichard Marian Thomaiyar     throwForUserNameConstraints(userName, groupNames);
3519f630d9eSRichard Marian Thomaiyar     throwForMaxGrpUserCount(groupNames);
3529f630d9eSRichard Marian Thomaiyar 
3539f630d9eSRichard Marian Thomaiyar     std::string groups = getCSVFromVector(groupNames);
3549f630d9eSRichard Marian Thomaiyar     bool sshRequested = removeStringFromCSV(groups, grpSsh);
3559f630d9eSRichard Marian Thomaiyar 
3569f630d9eSRichard Marian Thomaiyar     // treat privilege as a group - This is to avoid using different file to
3579f630d9eSRichard Marian Thomaiyar     // store the same.
3582cb2e720SRichard Marian Thomaiyar     if (!priv.empty())
3592cb2e720SRichard Marian Thomaiyar     {
3609f630d9eSRichard Marian Thomaiyar         if (groups.size() != 0)
3619f630d9eSRichard Marian Thomaiyar         {
3629f630d9eSRichard Marian Thomaiyar             groups += ",";
3639f630d9eSRichard Marian Thomaiyar         }
3649f630d9eSRichard Marian Thomaiyar         groups += priv;
3652cb2e720SRichard Marian Thomaiyar     }
3669f630d9eSRichard Marian Thomaiyar     try
3679f630d9eSRichard Marian Thomaiyar     {
36849c81364SNan Zhou         executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
3699f630d9eSRichard Marian Thomaiyar     }
3709f630d9eSRichard Marian Thomaiyar     catch (const InternalFailure& e)
3719f630d9eSRichard Marian Thomaiyar     {
37211ec666bSJiaqing Zhao         lg2::error("Unable to create new user '{USERNAME}'", "USERNAME",
37311ec666bSJiaqing Zhao                    userName);
3749f630d9eSRichard Marian Thomaiyar         elog<InternalFailure>();
3759f630d9eSRichard Marian Thomaiyar     }
3769f630d9eSRichard Marian Thomaiyar 
3779f630d9eSRichard Marian Thomaiyar     // Add the users object before sending out the signal
378b01e2fe7SP Dheeraj Srujan Kumar     sdbusplus::message::object_path tempObjPath(usersObjPath);
379b01e2fe7SP Dheeraj Srujan Kumar     tempObjPath /= userName;
380b01e2fe7SP Dheeraj Srujan Kumar     std::string userObj(tempObjPath);
3819f630d9eSRichard Marian Thomaiyar     std::sort(groupNames.begin(), groupNames.end());
3829f630d9eSRichard Marian Thomaiyar     usersList.emplace(
38378d85042SNan Zhou         userName, std::make_unique<phosphor::user::Users>(
38478d85042SNan Zhou                       bus, userObj.c_str(), groupNames, priv, enabled, *this));
385*93804ebaSAbhilash Raju     serializer.store();
38611ec666bSJiaqing Zhao     lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
3879f630d9eSRichard Marian Thomaiyar     return;
3889f630d9eSRichard Marian Thomaiyar }
3899f630d9eSRichard Marian Thomaiyar 
deleteUser(std::string userName)3909f630d9eSRichard Marian Thomaiyar void UserMgr::deleteUser(std::string userName)
3919f630d9eSRichard Marian Thomaiyar {
3929f630d9eSRichard Marian Thomaiyar     // All user management lock has to be based on /etc/shadow
393a260f187SAndrew Geissler     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
3949f630d9eSRichard Marian Thomaiyar     throwForUserDoesNotExist(userName);
3959f630d9eSRichard Marian Thomaiyar     try
3969f630d9eSRichard Marian Thomaiyar     {
397ac921a5eSJayanth Othayoth         // Clear user fail records
398ac921a5eSJayanth Othayoth         executeUserClearFailRecords(userName.c_str());
3994b29462cSPatrick Williams 
4004b29462cSPatrick Williams         executeUserDelete(userName.c_str());
4019f630d9eSRichard Marian Thomaiyar     }
4029f630d9eSRichard Marian Thomaiyar     catch (const InternalFailure& e)
4039f630d9eSRichard Marian Thomaiyar     {
40411ec666bSJiaqing Zhao         lg2::error("Delete User '{USERNAME}' failed", "USERNAME", userName);
4059f630d9eSRichard Marian Thomaiyar         elog<InternalFailure>();
4069f630d9eSRichard Marian Thomaiyar     }
4079f630d9eSRichard Marian Thomaiyar 
4089f630d9eSRichard Marian Thomaiyar     usersList.erase(userName);
409*93804ebaSAbhilash Raju     serializer.store();
41011ec666bSJiaqing Zhao     lg2::info("User '{USERNAME}' deleted successfully", "USERNAME", userName);
4119f630d9eSRichard Marian Thomaiyar     return;
4129f630d9eSRichard Marian Thomaiyar }
4139f630d9eSRichard Marian Thomaiyar 
checkDeleteGroupConstraints(const std::string & groupName)414da401fe5SNan Zhou void UserMgr::checkDeleteGroupConstraints(const std::string& groupName)
415da401fe5SNan Zhou {
416da401fe5SNan Zhou     if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) ==
417da401fe5SNan Zhou         groupsMgr.end())
418da401fe5SNan Zhou     {
41911ec666bSJiaqing Zhao         lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
420da401fe5SNan Zhou         elog<GroupNameDoesNotExists>();
421da401fe5SNan Zhou     }
422da401fe5SNan Zhou     checkAndThrowsForGroupChangeAllowed(groupName);
423da401fe5SNan Zhou }
424da401fe5SNan Zhou 
deleteGroup(std::string groupName)425da401fe5SNan Zhou void UserMgr::deleteGroup(std::string groupName)
426da401fe5SNan Zhou {
427da401fe5SNan Zhou     checkDeleteGroupConstraints(groupName);
428da401fe5SNan Zhou     try
429da401fe5SNan Zhou     {
430da401fe5SNan Zhou         executeGroupDeletion(groupName.c_str());
431da401fe5SNan Zhou     }
432da401fe5SNan Zhou     catch (const InternalFailure& e)
433da401fe5SNan Zhou     {
43411ec666bSJiaqing Zhao         lg2::error("Failed to delete group '{GROUP}'", "GROUP", groupName);
435da401fe5SNan Zhou         elog<InternalFailure>();
436da401fe5SNan Zhou     }
437da401fe5SNan Zhou 
438da401fe5SNan Zhou     groupsMgr.erase(std::find(groupsMgr.begin(), groupsMgr.end(), groupName));
439da401fe5SNan Zhou     UserMgrIface::allGroups(groupsMgr);
44011ec666bSJiaqing Zhao     lg2::info("Successfully deleted group '{GROUP}'", "GROUP", groupName);
441da401fe5SNan Zhou }
442da401fe5SNan Zhou 
checkCreateGroupConstraints(const std::string & groupName)443da401fe5SNan Zhou void UserMgr::checkCreateGroupConstraints(const std::string& groupName)
444da401fe5SNan Zhou {
445da401fe5SNan Zhou     if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) !=
446da401fe5SNan Zhou         groupsMgr.end())
447da401fe5SNan Zhou     {
44811ec666bSJiaqing Zhao         lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
449da401fe5SNan Zhou         elog<GroupNameExists>();
450da401fe5SNan Zhou     }
451da401fe5SNan Zhou     checkAndThrowForDisallowedGroupCreation(groupName);
452da401fe5SNan Zhou     if (groupsMgr.size() >= maxSystemGroupCount)
453da401fe5SNan Zhou     {
45411ec666bSJiaqing Zhao         lg2::error("Group limit reached");
455da401fe5SNan Zhou         elog<NoResource>(xyz::openbmc_project::User::Common::NoResource::REASON(
456da401fe5SNan Zhou             "Group limit reached"));
457da401fe5SNan Zhou     }
458da401fe5SNan Zhou }
459da401fe5SNan Zhou 
createGroup(std::string groupName)460da401fe5SNan Zhou void UserMgr::createGroup(std::string groupName)
461da401fe5SNan Zhou {
462da401fe5SNan Zhou     checkCreateGroupConstraints(groupName);
463da401fe5SNan Zhou     try
464da401fe5SNan Zhou     {
465da401fe5SNan Zhou         executeGroupCreation(groupName.c_str());
466da401fe5SNan Zhou     }
467da401fe5SNan Zhou     catch (const InternalFailure& e)
468da401fe5SNan Zhou     {
46911ec666bSJiaqing Zhao         lg2::error("Failed to create group '{GROUP}'", "GROUP", groupName);
470da401fe5SNan Zhou         elog<InternalFailure>();
471da401fe5SNan Zhou     }
472da401fe5SNan Zhou     groupsMgr.push_back(groupName);
473da401fe5SNan Zhou     UserMgrIface::allGroups(groupsMgr);
474da401fe5SNan Zhou }
475da401fe5SNan Zhou 
renameUser(std::string userName,std::string newUserName)4769f630d9eSRichard Marian Thomaiyar void UserMgr::renameUser(std::string userName, std::string newUserName)
4779f630d9eSRichard Marian Thomaiyar {
4789f630d9eSRichard Marian Thomaiyar     // All user management lock has to be based on /etc/shadow
479a260f187SAndrew Geissler     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
4809f630d9eSRichard Marian Thomaiyar     throwForUserDoesNotExist(userName);
4819f630d9eSRichard Marian Thomaiyar     throwForUserExists(newUserName);
4829f630d9eSRichard Marian Thomaiyar     throwForUserNameConstraints(newUserName,
4839f630d9eSRichard Marian Thomaiyar                                 usersList[userName].get()->userGroups());
4849f630d9eSRichard Marian Thomaiyar     try
4859f630d9eSRichard Marian Thomaiyar     {
486f25443e8SNan Zhou         executeUserRename(userName.c_str(), newUserName.c_str());
4879f630d9eSRichard Marian Thomaiyar     }
4889f630d9eSRichard Marian Thomaiyar     catch (const InternalFailure& e)
4899f630d9eSRichard Marian Thomaiyar     {
49011ec666bSJiaqing Zhao         lg2::error("Rename '{USERNAME}' to '{NEWUSERNAME}' failed", "USERNAME",
49111ec666bSJiaqing Zhao                    userName, "NEWUSERNAME", newUserName);
4929f630d9eSRichard Marian Thomaiyar         elog<InternalFailure>();
4939f630d9eSRichard Marian Thomaiyar     }
4949f630d9eSRichard Marian Thomaiyar     const auto& user = usersList[userName];
4959f630d9eSRichard Marian Thomaiyar     std::string priv = user.get()->userPrivilege();
4969f630d9eSRichard Marian Thomaiyar     std::vector<std::string> groupNames = user.get()->userGroups();
4979f630d9eSRichard Marian Thomaiyar     bool enabled = user.get()->userEnabled();
498b01e2fe7SP Dheeraj Srujan Kumar     sdbusplus::message::object_path tempObjPath(usersObjPath);
499b01e2fe7SP Dheeraj Srujan Kumar     tempObjPath /= newUserName;
500b01e2fe7SP Dheeraj Srujan Kumar     std::string newUserObj(tempObjPath);
5019f630d9eSRichard Marian Thomaiyar     // Special group 'ipmi' needs a way to identify user renamed, in order to
5029f630d9eSRichard Marian Thomaiyar     // update encrypted password. It can't rely only on InterfacesRemoved &
5039f630d9eSRichard Marian Thomaiyar     // InterfacesAdded. So first send out userRenamed signal.
5049f630d9eSRichard Marian Thomaiyar     this->userRenamed(userName, newUserName);
5059f630d9eSRichard Marian Thomaiyar     usersList.erase(userName);
50678d85042SNan Zhou     usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
50778d85042SNan Zhou                                        bus, newUserObj.c_str(), groupNames,
50878d85042SNan Zhou                                        priv, enabled, *this));
5099f630d9eSRichard Marian Thomaiyar     return;
5109f630d9eSRichard Marian Thomaiyar }
5119f630d9eSRichard Marian Thomaiyar 
updateGroupsAndPriv(const std::string & userName,std::vector<std::string> groupNames,const std::string & priv)5129f630d9eSRichard Marian Thomaiyar void UserMgr::updateGroupsAndPriv(const std::string& userName,
513fef63038SNan Zhou                                   std::vector<std::string> groupNames,
5149f630d9eSRichard Marian Thomaiyar                                   const std::string& priv)
5159f630d9eSRichard Marian Thomaiyar {
5169f630d9eSRichard Marian Thomaiyar     throwForInvalidPrivilege(priv);
5179f630d9eSRichard Marian Thomaiyar     throwForInvalidGroups(groupNames);
5189f630d9eSRichard Marian Thomaiyar     // All user management lock has to be based on /etc/shadow
519a260f187SAndrew Geissler     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
5209f630d9eSRichard Marian Thomaiyar     throwForUserDoesNotExist(userName);
5219f630d9eSRichard Marian Thomaiyar     const std::vector<std::string>& oldGroupNames =
5229f630d9eSRichard Marian Thomaiyar         usersList[userName].get()->userGroups();
5239f630d9eSRichard Marian Thomaiyar     std::vector<std::string> groupDiff;
5249f630d9eSRichard Marian Thomaiyar     // Note: already dealing with sorted group lists.
5259f630d9eSRichard Marian Thomaiyar     std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
5269f630d9eSRichard Marian Thomaiyar                                   groupNames.begin(), groupNames.end(),
5279f630d9eSRichard Marian Thomaiyar                                   std::back_inserter(groupDiff));
5289f630d9eSRichard Marian Thomaiyar     if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
5299f630d9eSRichard Marian Thomaiyar         groupDiff.end())
5309f630d9eSRichard Marian Thomaiyar     {
5319f630d9eSRichard Marian Thomaiyar         throwForUserNameConstraints(userName, groupNames);
5329f630d9eSRichard Marian Thomaiyar         throwForMaxGrpUserCount(groupNames);
5339f630d9eSRichard Marian Thomaiyar     }
5349f630d9eSRichard Marian Thomaiyar 
5359f630d9eSRichard Marian Thomaiyar     std::string groups = getCSVFromVector(groupNames);
5369f630d9eSRichard Marian Thomaiyar     bool sshRequested = removeStringFromCSV(groups, grpSsh);
5379f630d9eSRichard Marian Thomaiyar 
5389f630d9eSRichard Marian Thomaiyar     // treat privilege as a group - This is to avoid using different file to
5399f630d9eSRichard Marian Thomaiyar     // store the same.
5402cb2e720SRichard Marian Thomaiyar     if (!priv.empty())
5412cb2e720SRichard Marian Thomaiyar     {
5429f630d9eSRichard Marian Thomaiyar         if (groups.size() != 0)
5439f630d9eSRichard Marian Thomaiyar         {
5449f630d9eSRichard Marian Thomaiyar             groups += ",";
5459f630d9eSRichard Marian Thomaiyar         }
5469f630d9eSRichard Marian Thomaiyar         groups += priv;
5472cb2e720SRichard Marian Thomaiyar     }
5489f630d9eSRichard Marian Thomaiyar     try
5499f630d9eSRichard Marian Thomaiyar     {
550fef63038SNan Zhou         executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
5519f630d9eSRichard Marian Thomaiyar     }
5529f630d9eSRichard Marian Thomaiyar     catch (const InternalFailure& e)
5539f630d9eSRichard Marian Thomaiyar     {
55411ec666bSJiaqing Zhao         lg2::error(
55511ec666bSJiaqing Zhao             "Unable to modify user privilege / groups for user '{USERNAME}'",
55611ec666bSJiaqing Zhao             "USERNAME", userName);
5579f630d9eSRichard Marian Thomaiyar         elog<InternalFailure>();
5589f630d9eSRichard Marian Thomaiyar     }
5599f630d9eSRichard Marian Thomaiyar 
560fef63038SNan Zhou     std::sort(groupNames.begin(), groupNames.end());
561fef63038SNan Zhou     usersList[userName]->setUserGroups(groupNames);
562fef63038SNan Zhou     usersList[userName]->setUserPrivilege(priv);
56311ec666bSJiaqing Zhao     lg2::info("User '{USERNAME}' groups / privilege updated successfully",
56411ec666bSJiaqing Zhao               "USERNAME", userName);
5659f630d9eSRichard Marian Thomaiyar }
5669f630d9eSRichard Marian Thomaiyar 
minPasswordLength(uint8_t value)5679164fd9bSRichard Marian Thomaiyar uint8_t UserMgr::minPasswordLength(uint8_t value)
5689164fd9bSRichard Marian Thomaiyar {
5699164fd9bSRichard Marian Thomaiyar     if (value == AccountPolicyIface::minPasswordLength())
5709164fd9bSRichard Marian Thomaiyar     {
5719164fd9bSRichard Marian Thomaiyar         return value;
5729164fd9bSRichard Marian Thomaiyar     }
5739164fd9bSRichard Marian Thomaiyar     if (value < minPasswdLength)
5749164fd9bSRichard Marian Thomaiyar     {
57511ec666bSJiaqing Zhao         lg2::error("Attempting to set minPasswordLength to {VALUE}, less than "
57611ec666bSJiaqing Zhao                    "{MINVALUE}",
57711ec666bSJiaqing Zhao                    "VALUE", value, "MINVALUE", minPasswdLength);
5786dc7ed95SPaul Fertser         elog<InvalidArgument>(
5796dc7ed95SPaul Fertser             Argument::ARGUMENT_NAME("minPasswordLength"),
5806dc7ed95SPaul Fertser             Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
5819164fd9bSRichard Marian Thomaiyar     }
5822d042d14SJason M. Bills     if (setPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
5839164fd9bSRichard Marian Thomaiyar                               std::to_string(value)) != success)
5849164fd9bSRichard Marian Thomaiyar     {
58511ec666bSJiaqing Zhao         lg2::error("Unable to set minPasswordLength to {VALUE}", "VALUE",
58611ec666bSJiaqing Zhao                    value);
5879164fd9bSRichard Marian Thomaiyar         elog<InternalFailure>();
5889164fd9bSRichard Marian Thomaiyar     }
5899164fd9bSRichard Marian Thomaiyar     return AccountPolicyIface::minPasswordLength(value);
5909164fd9bSRichard Marian Thomaiyar }
5919164fd9bSRichard Marian Thomaiyar 
rememberOldPasswordTimes(uint8_t value)5929164fd9bSRichard Marian Thomaiyar uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
5939164fd9bSRichard Marian Thomaiyar {
5949164fd9bSRichard Marian Thomaiyar     if (value == AccountPolicyIface::rememberOldPasswordTimes())
5959164fd9bSRichard Marian Thomaiyar     {
5969164fd9bSRichard Marian Thomaiyar         return value;
5979164fd9bSRichard Marian Thomaiyar     }
5983b280ec7SJason M. Bills     if (setPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
5999164fd9bSRichard Marian Thomaiyar                               std::to_string(value)) != success)
6009164fd9bSRichard Marian Thomaiyar     {
60111ec666bSJiaqing Zhao         lg2::error("Unable to set rememberOldPasswordTimes to {VALUE}", "VALUE",
60211ec666bSJiaqing Zhao                    value);
6039164fd9bSRichard Marian Thomaiyar         elog<InternalFailure>();
6049164fd9bSRichard Marian Thomaiyar     }
6059164fd9bSRichard Marian Thomaiyar     return AccountPolicyIface::rememberOldPasswordTimes(value);
6069164fd9bSRichard Marian Thomaiyar }
6079164fd9bSRichard Marian Thomaiyar 
maxLoginAttemptBeforeLockout(uint16_t value)6089164fd9bSRichard Marian Thomaiyar uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
6099164fd9bSRichard Marian Thomaiyar {
6109164fd9bSRichard Marian Thomaiyar     if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
6119164fd9bSRichard Marian Thomaiyar     {
6129164fd9bSRichard Marian Thomaiyar         return value;
6139164fd9bSRichard Marian Thomaiyar     }
6142d042d14SJason M. Bills     if (setPamModuleConfValue(faillockConfigFile, maxFailedAttempt,
6159164fd9bSRichard Marian Thomaiyar                               std::to_string(value)) != success)
6169164fd9bSRichard Marian Thomaiyar     {
61711ec666bSJiaqing Zhao         lg2::error("Unable to set maxLoginAttemptBeforeLockout to {VALUE}",
61811ec666bSJiaqing Zhao                    "VALUE", value);
6199164fd9bSRichard Marian Thomaiyar         elog<InternalFailure>();
6209164fd9bSRichard Marian Thomaiyar     }
6219164fd9bSRichard Marian Thomaiyar     return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
6229164fd9bSRichard Marian Thomaiyar }
6239164fd9bSRichard Marian Thomaiyar 
accountUnlockTimeout(uint32_t value)6249164fd9bSRichard Marian Thomaiyar uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
6259164fd9bSRichard Marian Thomaiyar {
6269164fd9bSRichard Marian Thomaiyar     if (value == AccountPolicyIface::accountUnlockTimeout())
6279164fd9bSRichard Marian Thomaiyar     {
6289164fd9bSRichard Marian Thomaiyar         return value;
6299164fd9bSRichard Marian Thomaiyar     }
6302d042d14SJason M. Bills     if (setPamModuleConfValue(faillockConfigFile, unlockTimeout,
6312d042d14SJason M. Bills                               std::to_string(value)) != success)
6329164fd9bSRichard Marian Thomaiyar     {
63311ec666bSJiaqing Zhao         lg2::error("Unable to set accountUnlockTimeout to {VALUE}", "VALUE",
63411ec666bSJiaqing Zhao                    value);
6359164fd9bSRichard Marian Thomaiyar         elog<InternalFailure>();
6369164fd9bSRichard Marian Thomaiyar     }
6379164fd9bSRichard Marian Thomaiyar     return AccountPolicyIface::accountUnlockTimeout(value);
6389164fd9bSRichard Marian Thomaiyar }
6399164fd9bSRichard Marian Thomaiyar 
getPamModuleConfValue(const std::string & confFile,const std::string & argName,std::string & argValue)6402d042d14SJason M. Bills int UserMgr::getPamModuleConfValue(const std::string& confFile,
6412d042d14SJason M. Bills                                    const std::string& argName,
6422d042d14SJason M. Bills                                    std::string& argValue)
6432d042d14SJason M. Bills {
6442d042d14SJason M. Bills     std::ifstream fileToRead(confFile, std::ios::in);
6452d042d14SJason M. Bills     if (!fileToRead.is_open())
6462d042d14SJason M. Bills     {
6472d042d14SJason M. Bills         lg2::error("Failed to open pam configuration file {FILENAME}",
6482d042d14SJason M. Bills                    "FILENAME", confFile);
6492d042d14SJason M. Bills         return failure;
6502d042d14SJason M. Bills     }
6512d042d14SJason M. Bills     std::string line;
6522d042d14SJason M. Bills     auto argSearch = argName + "=";
6532d042d14SJason M. Bills     size_t startPos = 0;
6542d042d14SJason M. Bills     size_t endPos = 0;
6552d042d14SJason M. Bills     while (getline(fileToRead, line))
6562d042d14SJason M. Bills     {
6572d042d14SJason M. Bills         // skip comments section starting with #
6582d042d14SJason M. Bills         if ((startPos = line.find('#')) != std::string::npos)
6592d042d14SJason M. Bills         {
6602d042d14SJason M. Bills             if (startPos == 0)
6612d042d14SJason M. Bills             {
6622d042d14SJason M. Bills                 continue;
6632d042d14SJason M. Bills             }
6642d042d14SJason M. Bills             // skip comments after meaningful section and process those
6652d042d14SJason M. Bills             line = line.substr(0, startPos);
6662d042d14SJason M. Bills         }
6672d042d14SJason M. Bills         if ((startPos = line.find(argSearch)) != std::string::npos)
6682d042d14SJason M. Bills         {
6692d042d14SJason M. Bills             if ((endPos = line.find(' ', startPos)) == std::string::npos)
6702d042d14SJason M. Bills             {
6712d042d14SJason M. Bills                 endPos = line.size();
6722d042d14SJason M. Bills             }
6732d042d14SJason M. Bills             startPos += argSearch.size();
6742d042d14SJason M. Bills             argValue = line.substr(startPos, endPos - startPos);
6752d042d14SJason M. Bills             return success;
6762d042d14SJason M. Bills         }
6772d042d14SJason M. Bills     }
6782d042d14SJason M. Bills     return failure;
6792d042d14SJason M. Bills }
6802d042d14SJason M. Bills 
setPamModuleConfValue(const std::string & confFile,const std::string & argName,const std::string & argValue)6812d042d14SJason M. Bills int UserMgr::setPamModuleConfValue(const std::string& confFile,
6822d042d14SJason M. Bills                                    const std::string& argName,
6832d042d14SJason M. Bills                                    const std::string& argValue)
6842d042d14SJason M. Bills {
6852d042d14SJason M. Bills     std::string tmpConfFile = confFile + "_tmp";
6862d042d14SJason M. Bills     std::ifstream fileToRead(confFile, std::ios::in);
6872d042d14SJason M. Bills     std::ofstream fileToWrite(tmpConfFile, std::ios::out);
6882d042d14SJason M. Bills     if (!fileToRead.is_open() || !fileToWrite.is_open())
6892d042d14SJason M. Bills     {
6902d042d14SJason M. Bills         lg2::error("Failed to open pam configuration file {FILENAME}",
6912d042d14SJason M. Bills                    "FILENAME", confFile);
69217b88278SJason M. Bills         // Delete the unused tmp file
69317b88278SJason M. Bills         std::remove(tmpConfFile.c_str());
6942d042d14SJason M. Bills         return failure;
6952d042d14SJason M. Bills     }
6962d042d14SJason M. Bills     std::string line;
6972d042d14SJason M. Bills     auto argSearch = argName + "=";
6982d042d14SJason M. Bills     size_t startPos = 0;
6992d042d14SJason M. Bills     size_t endPos = 0;
7002d042d14SJason M. Bills     bool found = false;
7012d042d14SJason M. Bills     while (getline(fileToRead, line))
7022d042d14SJason M. Bills     {
7032d042d14SJason M. Bills         // skip comments section starting with #
7042d042d14SJason M. Bills         if ((startPos = line.find('#')) != std::string::npos)
7052d042d14SJason M. Bills         {
7062d042d14SJason M. Bills             if (startPos == 0)
7072d042d14SJason M. Bills             {
7082d042d14SJason M. Bills                 fileToWrite << line << std::endl;
7092d042d14SJason M. Bills                 continue;
7102d042d14SJason M. Bills             }
7112d042d14SJason M. Bills             // skip comments after meaningful section and process those
7122d042d14SJason M. Bills             line = line.substr(0, startPos);
7132d042d14SJason M. Bills         }
7142d042d14SJason M. Bills         if ((startPos = line.find(argSearch)) != std::string::npos)
7152d042d14SJason M. Bills         {
7162d042d14SJason M. Bills             if ((endPos = line.find(' ', startPos)) == std::string::npos)
7172d042d14SJason M. Bills             {
7182d042d14SJason M. Bills                 endPos = line.size();
7192d042d14SJason M. Bills             }
7202d042d14SJason M. Bills             startPos += argSearch.size();
7212d042d14SJason M. Bills             fileToWrite << line.substr(0, startPos) << argValue
7222d042d14SJason M. Bills                         << line.substr(endPos, line.size() - endPos)
7232d042d14SJason M. Bills                         << std::endl;
7242d042d14SJason M. Bills             found = true;
7252d042d14SJason M. Bills             continue;
7262d042d14SJason M. Bills         }
7272d042d14SJason M. Bills         fileToWrite << line << std::endl;
7282d042d14SJason M. Bills     }
7292d042d14SJason M. Bills     fileToWrite.close();
7302d042d14SJason M. Bills     fileToRead.close();
7312d042d14SJason M. Bills     if (found)
7322d042d14SJason M. Bills     {
7332d042d14SJason M. Bills         if (std::rename(tmpConfFile.c_str(), confFile.c_str()) == 0)
7342d042d14SJason M. Bills         {
7352d042d14SJason M. Bills             return success;
7362d042d14SJason M. Bills         }
7372d042d14SJason M. Bills     }
73817b88278SJason M. Bills     // No changes, so delete the unused tmp file
73917b88278SJason M. Bills     std::remove(tmpConfFile.c_str());
7402d042d14SJason M. Bills     return failure;
7412d042d14SJason M. Bills }
7422d042d14SJason M. Bills 
userEnable(const std::string & userName,bool enabled)7439f630d9eSRichard Marian Thomaiyar void UserMgr::userEnable(const std::string& userName, bool enabled)
7449f630d9eSRichard Marian Thomaiyar {
7459f630d9eSRichard Marian Thomaiyar     // All user management lock has to be based on /etc/shadow
746a260f187SAndrew Geissler     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
7479f630d9eSRichard Marian Thomaiyar     throwForUserDoesNotExist(userName);
7489f630d9eSRichard Marian Thomaiyar     try
7499f630d9eSRichard Marian Thomaiyar     {
7506b6f2d80SNan Zhou         executeUserModifyUserEnable(userName.c_str(), enabled);
7519f630d9eSRichard Marian Thomaiyar     }
7529f630d9eSRichard Marian Thomaiyar     catch (const InternalFailure& e)
7539f630d9eSRichard Marian Thomaiyar     {
75411ec666bSJiaqing Zhao         lg2::error("Unable to modify user enabled state for '{USERNAME}'",
75511ec666bSJiaqing Zhao                    "USERNAME", userName);
7569f630d9eSRichard Marian Thomaiyar         elog<InternalFailure>();
7579f630d9eSRichard Marian Thomaiyar     }
7589f630d9eSRichard Marian Thomaiyar 
7596b6f2d80SNan Zhou     usersList[userName]->setUserEnabled(enabled);
76011ec666bSJiaqing Zhao     lg2::info("User '{USERNAME}' has been {STATUS}", "USERNAME", userName,
76111ec666bSJiaqing Zhao               "STATUS", enabled ? "Enabled" : "Disabled");
7629f630d9eSRichard Marian Thomaiyar }
7639f630d9eSRichard Marian Thomaiyar 
764c704519eSRichard Marian Thomaiyar /**
7652d042d14SJason M. Bills  * faillock app will provide the user failed login list with when the attempt
7662d042d14SJason M. Bills  * was made, the type, the source, and if it's valid.
7672d042d14SJason M. Bills  *
7682d042d14SJason M. Bills  * Valid in this case means that the attempt was made within the fail_interval
7692d042d14SJason M. Bills  * time. So, we can check this list for the number of valid entries (lines
7702d042d14SJason M. Bills  * ending with 'V') compared to the maximum allowed to determine if the user is
7712d042d14SJason M. Bills  * locked out.
7722d042d14SJason M. Bills  *
7732d042d14SJason M. Bills  * This data is only refreshed when an attempt is made, so if the user appears
7742d042d14SJason M. Bills  * to be locked out, we must also check if the most recent attempt was older
7752d042d14SJason M. Bills  * than the unlock_time to know if the user has since been unlocked.
776c704519eSRichard Marian Thomaiyar  **/
parseFaillockForLockout(const std::vector<std::string> & faillockOutput)7772d042d14SJason M. Bills bool UserMgr::parseFaillockForLockout(
7782d042d14SJason M. Bills     const std::vector<std::string>& faillockOutput)
7792d042d14SJason M. Bills {
7802d042d14SJason M. Bills     uint16_t failAttempts = 0;
7812d042d14SJason M. Bills     time_t lastFailedAttempt{};
7822d042d14SJason M. Bills     for (const std::string& line : faillockOutput)
7832d042d14SJason M. Bills     {
7842d042d14SJason M. Bills         if (!line.ends_with("V"))
7852d042d14SJason M. Bills         {
7862d042d14SJason M. Bills             continue;
7872d042d14SJason M. Bills         }
788c704519eSRichard Marian Thomaiyar 
7892d042d14SJason M. Bills         // Count this failed attempt
7902d042d14SJason M. Bills         failAttempts++;
7912d042d14SJason M. Bills 
7922d042d14SJason M. Bills         // Update the last attempt time
7932d042d14SJason M. Bills         // First get the "when" which is the first two words (date and time)
7942d042d14SJason M. Bills         size_t pos = line.find(" ");
7952d042d14SJason M. Bills         if (pos == std::string::npos)
7962d042d14SJason M. Bills         {
7972d042d14SJason M. Bills             continue;
7982d042d14SJason M. Bills         }
7992d042d14SJason M. Bills         pos = line.find(" ", pos + 1);
8002d042d14SJason M. Bills         if (pos == std::string::npos)
8012d042d14SJason M. Bills         {
8022d042d14SJason M. Bills             continue;
8032d042d14SJason M. Bills         }
8042d042d14SJason M. Bills         std::string failDateTime = line.substr(0, pos);
8052d042d14SJason M. Bills 
8062d042d14SJason M. Bills         // NOTE: Cannot use std::get_time() here as the implementation of %y in
8072d042d14SJason M. Bills         // libstdc++ does not match POSIX strptime() before gcc 12.1.0
8082d042d14SJason M. Bills         // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
8092d042d14SJason M. Bills         std::tm tmStruct = {};
8102d042d14SJason M. Bills         if (!strptime(failDateTime.c_str(), "%F %T", &tmStruct))
8112d042d14SJason M. Bills         {
8122d042d14SJason M. Bills             lg2::error("Failed to parse latest failure date/time");
8132d042d14SJason M. Bills             elog<InternalFailure>();
8142d042d14SJason M. Bills         }
8152d042d14SJason M. Bills 
8162d042d14SJason M. Bills         time_t failTimestamp = std::mktime(&tmStruct);
8172d042d14SJason M. Bills         lastFailedAttempt = std::max(failTimestamp, lastFailedAttempt);
8182d042d14SJason M. Bills     }
8192d042d14SJason M. Bills 
8202d042d14SJason M. Bills     if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
8212d042d14SJason M. Bills     {
8222d042d14SJason M. Bills         return false;
8232d042d14SJason M. Bills     }
8242d042d14SJason M. Bills 
8252d042d14SJason M. Bills     if (lastFailedAttempt +
8262d042d14SJason M. Bills             static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
8272d042d14SJason M. Bills         std::time(NULL))
8282d042d14SJason M. Bills     {
8292d042d14SJason M. Bills         return false;
8302d042d14SJason M. Bills     }
8312d042d14SJason M. Bills 
8322d042d14SJason M. Bills     return true;
8332d042d14SJason M. Bills }
834c704519eSRichard Marian Thomaiyar 
userLockedForFailedAttempt(const std::string & userName)835c704519eSRichard Marian Thomaiyar bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
836c704519eSRichard Marian Thomaiyar {
837c704519eSRichard Marian Thomaiyar     // All user management lock has to be based on /etc/shadow
838a260f187SAndrew Geissler     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
839fba4bb17SJiaqing Zhao     if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
840fba4bb17SJiaqing Zhao     {
841fba4bb17SJiaqing Zhao         return false;
842fba4bb17SJiaqing Zhao     }
843fba4bb17SJiaqing Zhao 
844c704519eSRichard Marian Thomaiyar     std::vector<std::string> output;
8458557d320SJiaqing Zhao     try
8468557d320SJiaqing Zhao     {
847a295303bSNan Zhou         output = getFailedAttempt(userName.c_str());
8488557d320SJiaqing Zhao     }
8498557d320SJiaqing Zhao     catch (const InternalFailure& e)
8508557d320SJiaqing Zhao     {
85111ec666bSJiaqing Zhao         lg2::error("Unable to read login failure counter");
8528557d320SJiaqing Zhao         elog<InternalFailure>();
8538557d320SJiaqing Zhao     }
854c704519eSRichard Marian Thomaiyar 
8552d042d14SJason M. Bills     return parseFaillockForLockout(output);
856c704519eSRichard Marian Thomaiyar }
857c704519eSRichard Marian Thomaiyar 
userLockedForFailedAttempt(const std::string & userName,const bool & value)858c704519eSRichard Marian Thomaiyar bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
859c704519eSRichard Marian Thomaiyar                                          const bool& value)
860c704519eSRichard Marian Thomaiyar {
861c704519eSRichard Marian Thomaiyar     // All user management lock has to be based on /etc/shadow
862a260f187SAndrew Geissler     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
863c704519eSRichard Marian Thomaiyar     if (value == true)
864c704519eSRichard Marian Thomaiyar     {
865c704519eSRichard Marian Thomaiyar         return userLockedForFailedAttempt(userName);
866c704519eSRichard Marian Thomaiyar     }
867c704519eSRichard Marian Thomaiyar 
8688557d320SJiaqing Zhao     try
8698557d320SJiaqing Zhao     {
870ac921a5eSJayanth Othayoth         // Clear user fail records
871ac921a5eSJayanth Othayoth         executeUserClearFailRecords(userName.c_str());
8728557d320SJiaqing Zhao     }
8738557d320SJiaqing Zhao     catch (const InternalFailure& e)
8748557d320SJiaqing Zhao     {
87511ec666bSJiaqing Zhao         lg2::error("Unable to reset login failure counter");
8768557d320SJiaqing Zhao         elog<InternalFailure>();
8778557d320SJiaqing Zhao     }
878c704519eSRichard Marian Thomaiyar 
879c704519eSRichard Marian Thomaiyar     return userLockedForFailedAttempt(userName);
880c704519eSRichard Marian Thomaiyar }
881c704519eSRichard Marian Thomaiyar 
userPasswordExpired(const std::string & userName)8823ab6cc28SJoseph Reynolds bool UserMgr::userPasswordExpired(const std::string& userName)
8833ab6cc28SJoseph Reynolds {
8843ab6cc28SJoseph Reynolds     // All user management lock has to be based on /etc/shadow
885a260f187SAndrew Geissler     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
8863ab6cc28SJoseph Reynolds 
88766addf25SPatrick Williams     struct spwd spwd{};
8883ab6cc28SJoseph Reynolds     struct spwd* spwdPtr = nullptr;
8893ab6cc28SJoseph Reynolds     auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
89034e6ccd4SGeorge Liu     if (buflen <= 0)
8913ab6cc28SJoseph Reynolds     {
8923ab6cc28SJoseph Reynolds         // Use a default size if there is no hard limit suggested by sysconf()
8933ab6cc28SJoseph Reynolds         buflen = 1024;
8943ab6cc28SJoseph Reynolds     }
8953ab6cc28SJoseph Reynolds     std::vector<char> buffer(buflen);
89616c2b681SPatrick Williams     auto status =
89716c2b681SPatrick Williams         getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
8983ab6cc28SJoseph Reynolds     // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
8993ab6cc28SJoseph Reynolds     // If no matching password record was found, these functions return 0
9003ab6cc28SJoseph Reynolds     // and store NULL in *spwdPtr
9013ab6cc28SJoseph Reynolds     if ((status == 0) && (&spwd == spwdPtr))
9023ab6cc28SJoseph Reynolds     {
9033ab6cc28SJoseph Reynolds         // Determine password validity per "chage" docs, where:
9043ab6cc28SJoseph Reynolds         //   spwd.sp_lstchg == 0 means password is expired, and
9053ab6cc28SJoseph Reynolds         //   spwd.sp_max == -1 means the password does not expire.
90678d85042SNan Zhou         constexpr long secondsPerDay = 60 * 60 * 24;
90778d85042SNan Zhou         long today = static_cast<long>(time(NULL)) / secondsPerDay;
9083ab6cc28SJoseph Reynolds         if ((spwd.sp_lstchg == 0) ||
9093ab6cc28SJoseph Reynolds             ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
9103ab6cc28SJoseph Reynolds         {
9113ab6cc28SJoseph Reynolds             return true;
9123ab6cc28SJoseph Reynolds         }
9133ab6cc28SJoseph Reynolds     }
9143ab6cc28SJoseph Reynolds     else
9153ab6cc28SJoseph Reynolds     {
91675be4e68SJayaprakash Mutyala         // User entry is missing in /etc/shadow, indicating no SHA password.
91775be4e68SJayaprakash Mutyala         // Treat this as new user without password entry in /etc/shadow
91875be4e68SJayaprakash Mutyala         // TODO: Add property to indicate user password was not set yet
91975be4e68SJayaprakash Mutyala         // https://github.com/openbmc/phosphor-user-manager/issues/8
92075be4e68SJayaprakash Mutyala         return false;
9213ab6cc28SJoseph Reynolds     }
9223ab6cc28SJoseph Reynolds 
9233ab6cc28SJoseph Reynolds     return false;
9243ab6cc28SJoseph Reynolds }
9253ab6cc28SJoseph Reynolds 
getUserAndSshGrpList()9269f630d9eSRichard Marian Thomaiyar UserSSHLists UserMgr::getUserAndSshGrpList()
9279f630d9eSRichard Marian Thomaiyar {
9289f630d9eSRichard Marian Thomaiyar     // All user management lock has to be based on /etc/shadow
929a260f187SAndrew Geissler     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
9309f630d9eSRichard Marian Thomaiyar 
9319f630d9eSRichard Marian Thomaiyar     std::vector<std::string> userList;
9329f630d9eSRichard Marian Thomaiyar     std::vector<std::string> sshUsersList;
9339f630d9eSRichard Marian Thomaiyar     struct passwd pw, *pwp = nullptr;
9349f630d9eSRichard Marian Thomaiyar     std::array<char, 1024> buffer{};
9359f630d9eSRichard Marian Thomaiyar 
9369f630d9eSRichard Marian Thomaiyar     phosphor::user::File passwd(passwdFileName, "r");
9379f630d9eSRichard Marian Thomaiyar     if ((passwd)() == NULL)
9389f630d9eSRichard Marian Thomaiyar     {
93911ec666bSJiaqing Zhao         lg2::error("Error opening {FILENAME}", "FILENAME", passwdFileName);
9409f630d9eSRichard Marian Thomaiyar         elog<InternalFailure>();
9419f630d9eSRichard Marian Thomaiyar     }
9429f630d9eSRichard Marian Thomaiyar 
9439f630d9eSRichard Marian Thomaiyar     while (true)
9449f630d9eSRichard Marian Thomaiyar     {
9459f630d9eSRichard Marian Thomaiyar         auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
9469f630d9eSRichard Marian Thomaiyar                              &pwp);
9479f630d9eSRichard Marian Thomaiyar         if ((r != 0) || (pwp == NULL))
9489f630d9eSRichard Marian Thomaiyar         {
9499f630d9eSRichard Marian Thomaiyar             // Any error, break the loop.
9509f630d9eSRichard Marian Thomaiyar             break;
9519f630d9eSRichard Marian Thomaiyar         }
952d4d65500SRichard Marian Thomaiyar #ifdef ENABLE_ROOT_USER_MGMT
9537ba3c71cSRichard Marian Thomaiyar         // Add all users whose UID >= 1000 and < 65534
9547ba3c71cSRichard Marian Thomaiyar         // and special UID 0.
9557ba3c71cSRichard Marian Thomaiyar         if ((pwp->pw_uid == 0) ||
9567ba3c71cSRichard Marian Thomaiyar             ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
957d4d65500SRichard Marian Thomaiyar #else
958d4d65500SRichard Marian Thomaiyar         // Add all users whose UID >=1000 and < 65534
959d4d65500SRichard Marian Thomaiyar         if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
960d4d65500SRichard Marian Thomaiyar #endif
9619f630d9eSRichard Marian Thomaiyar         {
9629f630d9eSRichard Marian Thomaiyar             std::string userName(pwp->pw_name);
9639f630d9eSRichard Marian Thomaiyar             userList.emplace_back(userName);
9649f630d9eSRichard Marian Thomaiyar 
9659f630d9eSRichard Marian Thomaiyar             // ssh doesn't have separate group. Check login shell entry to
9669f630d9eSRichard Marian Thomaiyar             // get all users list which are member of ssh group.
9679f630d9eSRichard Marian Thomaiyar             std::string loginShell(pwp->pw_shell);
9689f630d9eSRichard Marian Thomaiyar             if (loginShell == "/bin/sh")
9699f630d9eSRichard Marian Thomaiyar             {
9709f630d9eSRichard Marian Thomaiyar                 sshUsersList.emplace_back(userName);
9719f630d9eSRichard Marian Thomaiyar             }
9729f630d9eSRichard Marian Thomaiyar         }
9739f630d9eSRichard Marian Thomaiyar     }
9749f630d9eSRichard Marian Thomaiyar     endpwent();
9759f630d9eSRichard Marian Thomaiyar     return std::make_pair(std::move(userList), std::move(sshUsersList));
9769f630d9eSRichard Marian Thomaiyar }
9779f630d9eSRichard Marian Thomaiyar 
getIpmiUsersCount()9789f630d9eSRichard Marian Thomaiyar size_t UserMgr::getIpmiUsersCount()
9799f630d9eSRichard Marian Thomaiyar {
9809f630d9eSRichard Marian Thomaiyar     std::vector<std::string> userList = getUsersInGroup("ipmi");
9819f630d9eSRichard Marian Thomaiyar     return userList.size();
9829f630d9eSRichard Marian Thomaiyar }
9839f630d9eSRichard Marian Thomaiyar 
getNonIpmiUsersCount()98449c81364SNan Zhou size_t UserMgr::getNonIpmiUsersCount()
98549c81364SNan Zhou {
98649c81364SNan Zhou     std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
98749c81364SNan Zhou     return usersList.size() - ipmiUsers.size();
98849c81364SNan Zhou }
98949c81364SNan Zhou 
isUserEnabled(const std::string & userName)9909f630d9eSRichard Marian Thomaiyar bool UserMgr::isUserEnabled(const std::string& userName)
9919f630d9eSRichard Marian Thomaiyar {
9929f630d9eSRichard Marian Thomaiyar     // All user management lock has to be based on /etc/shadow
993a260f187SAndrew Geissler     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
9949f630d9eSRichard Marian Thomaiyar     std::array<char, 4096> buffer{};
9959f630d9eSRichard Marian Thomaiyar     struct spwd spwd;
9969f630d9eSRichard Marian Thomaiyar     struct spwd* resultPtr = nullptr;
9979f630d9eSRichard Marian Thomaiyar     int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
9989f630d9eSRichard Marian Thomaiyar                             buffer.max_size(), &resultPtr);
9999f630d9eSRichard Marian Thomaiyar     if (!status && (&spwd == resultPtr))
10009f630d9eSRichard Marian Thomaiyar     {
10019f630d9eSRichard Marian Thomaiyar         if (resultPtr->sp_expire >= 0)
10029f630d9eSRichard Marian Thomaiyar         {
10039f630d9eSRichard Marian Thomaiyar             return false; // user locked out
10049f630d9eSRichard Marian Thomaiyar         }
10059f630d9eSRichard Marian Thomaiyar         return true;
10069f630d9eSRichard Marian Thomaiyar     }
10079f630d9eSRichard Marian Thomaiyar     return false; // assume user is disabled for any error.
10089f630d9eSRichard Marian Thomaiyar }
10099f630d9eSRichard Marian Thomaiyar 
getUsersInGroup(const std::string & groupName)10109f630d9eSRichard Marian Thomaiyar std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
10119f630d9eSRichard Marian Thomaiyar {
10129f630d9eSRichard Marian Thomaiyar     std::vector<std::string> usersInGroup;
10139f630d9eSRichard Marian Thomaiyar     // Should be more than enough to get the pwd structure.
10149f630d9eSRichard Marian Thomaiyar     std::array<char, 4096> buffer{};
10159f630d9eSRichard Marian Thomaiyar     struct group grp;
10169f630d9eSRichard Marian Thomaiyar     struct group* resultPtr = nullptr;
10179f630d9eSRichard Marian Thomaiyar 
10189f630d9eSRichard Marian Thomaiyar     int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
10199f630d9eSRichard Marian Thomaiyar                             buffer.max_size(), &resultPtr);
10209f630d9eSRichard Marian Thomaiyar 
10219f630d9eSRichard Marian Thomaiyar     if (!status && (&grp == resultPtr))
10229f630d9eSRichard Marian Thomaiyar     {
10239f630d9eSRichard Marian Thomaiyar         for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
10249f630d9eSRichard Marian Thomaiyar         {
10259f630d9eSRichard Marian Thomaiyar             usersInGroup.emplace_back(*(grp.gr_mem));
10269f630d9eSRichard Marian Thomaiyar         }
10279f630d9eSRichard Marian Thomaiyar     }
10289f630d9eSRichard Marian Thomaiyar     else
10299f630d9eSRichard Marian Thomaiyar     {
103011ec666bSJiaqing Zhao         lg2::error("Group '{GROUPNAME}' not found", "GROUPNAME", groupName);
10319f630d9eSRichard Marian Thomaiyar         // Don't throw error, just return empty userList - fallback
10329f630d9eSRichard Marian Thomaiyar     }
10339f630d9eSRichard Marian Thomaiyar     return usersInGroup;
10349f630d9eSRichard Marian Thomaiyar }
10359f630d9eSRichard Marian Thomaiyar 
getPrivilegeMapperObject(void)1036aeaf9413SRatan Gupta DbusUserObj UserMgr::getPrivilegeMapperObject(void)
1037aeaf9413SRatan Gupta {
1038aeaf9413SRatan Gupta     DbusUserObj objects;
1039aeaf9413SRatan Gupta     try
1040aeaf9413SRatan Gupta     {
10415fe724a7SRavi Teja         std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
10425fe724a7SRavi Teja         std::string interface = "xyz.openbmc_project.User.Ldap.Config";
1043aeaf9413SRatan Gupta 
104416c2b681SPatrick Williams         auto ldapMgmtService =
104516c2b681SPatrick Williams             getServiceName(std::move(basePath), std::move(interface));
1046aeaf9413SRatan Gupta         auto method = bus.new_method_call(
1047aeaf9413SRatan Gupta             ldapMgmtService.c_str(), ldapMgrObjBasePath,
1048aeaf9413SRatan Gupta             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1049aeaf9413SRatan Gupta 
1050aeaf9413SRatan Gupta         auto reply = bus.call(method);
1051aeaf9413SRatan Gupta         reply.read(objects);
1052aeaf9413SRatan Gupta     }
1053aeaf9413SRatan Gupta     catch (const InternalFailure& e)
1054aeaf9413SRatan Gupta     {
105511ec666bSJiaqing Zhao         lg2::error("Unable to get the User Service: {ERR}", "ERR", e);
1056aeaf9413SRatan Gupta         throw;
1057aeaf9413SRatan Gupta     }
1058b3ef4e1aSPatrick Williams     catch (const sdbusplus::exception_t& e)
1059aeaf9413SRatan Gupta     {
106046e773a9SManojkiran Eda         lg2::error("Failed to execute GetManagedObjects at {PATH}: {ERR}",
106111ec666bSJiaqing Zhao                    "PATH", ldapMgrObjBasePath, "ERR", e);
1062aeaf9413SRatan Gupta         throw;
1063aeaf9413SRatan Gupta     }
1064aeaf9413SRatan Gupta     return objects;
1065aeaf9413SRatan Gupta }
1066aeaf9413SRatan Gupta 
getServiceName(std::string && path,std::string && intf)1067aeaf9413SRatan Gupta std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
1068aeaf9413SRatan Gupta {
1069aeaf9413SRatan Gupta     auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1070aeaf9413SRatan Gupta                                           objMapperInterface, "GetObject");
1071aeaf9413SRatan Gupta 
1072aeaf9413SRatan Gupta     mapperCall.append(std::move(path));
1073aeaf9413SRatan Gupta     mapperCall.append(std::vector<std::string>({std::move(intf)}));
1074aeaf9413SRatan Gupta 
1075aeaf9413SRatan Gupta     auto mapperResponseMsg = bus.call(mapperCall);
1076aeaf9413SRatan Gupta 
1077aeaf9413SRatan Gupta     if (mapperResponseMsg.is_method_error())
1078aeaf9413SRatan Gupta     {
107911ec666bSJiaqing Zhao         lg2::error("Error in mapper call");
1080aeaf9413SRatan Gupta         elog<InternalFailure>();
1081aeaf9413SRatan Gupta     }
1082aeaf9413SRatan Gupta 
1083aeaf9413SRatan Gupta     std::map<std::string, std::vector<std::string>> mapperResponse;
1084aeaf9413SRatan Gupta     mapperResponseMsg.read(mapperResponse);
1085aeaf9413SRatan Gupta 
1086aeaf9413SRatan Gupta     if (mapperResponse.begin() == mapperResponse.end())
1087aeaf9413SRatan Gupta     {
108811ec666bSJiaqing Zhao         lg2::error("Invalid response from mapper");
1089aeaf9413SRatan Gupta         elog<InternalFailure>();
1090aeaf9413SRatan Gupta     }
1091aeaf9413SRatan Gupta 
1092aeaf9413SRatan Gupta     return mapperResponse.begin()->first;
1093aeaf9413SRatan Gupta }
1094aeaf9413SRatan Gupta 
getPrimaryGroup(const std::string & userName) const10957562658eSAlexander Filippov gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
10967562658eSAlexander Filippov {
10977562658eSAlexander Filippov     static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
10987562658eSAlexander Filippov     if (buflen <= 0)
10997562658eSAlexander Filippov     {
11007562658eSAlexander Filippov         // Use a default size if there is no hard limit suggested by sysconf()
11017562658eSAlexander Filippov         buflen = 1024;
11027562658eSAlexander Filippov     }
11037562658eSAlexander Filippov 
11047562658eSAlexander Filippov     struct passwd pwd;
11057562658eSAlexander Filippov     struct passwd* pwdPtr = nullptr;
11067562658eSAlexander Filippov     std::vector<char> buffer(buflen);
11077562658eSAlexander Filippov 
11087562658eSAlexander Filippov     auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(),
11097562658eSAlexander Filippov                              buffer.size(), &pwdPtr);
11107562658eSAlexander Filippov     // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
11117562658eSAlexander Filippov     // If no matching password record was found, these functions return 0
11127562658eSAlexander Filippov     // and store NULL in *pwdPtr
11137562658eSAlexander Filippov     if (!status && (&pwd == pwdPtr))
11147562658eSAlexander Filippov     {
11157562658eSAlexander Filippov         return pwd.pw_gid;
11167562658eSAlexander Filippov     }
11177562658eSAlexander Filippov 
111811ec666bSJiaqing Zhao     lg2::error("User {USERNAME} does not exist", "USERNAME", userName);
11197562658eSAlexander Filippov     elog<UserNameDoesNotExist>();
11207562658eSAlexander Filippov }
11217562658eSAlexander Filippov 
isGroupMember(const std::string & userName,gid_t primaryGid,const std::string & groupName) const11227562658eSAlexander Filippov bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
11237562658eSAlexander Filippov                             const std::string& groupName) const
11247562658eSAlexander Filippov {
11257562658eSAlexander Filippov     static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
11267562658eSAlexander Filippov     if (buflen <= 0)
11277562658eSAlexander Filippov     {
11287562658eSAlexander Filippov         // Use a default size if there is no hard limit suggested by sysconf()
11297562658eSAlexander Filippov         buflen = 1024;
11307562658eSAlexander Filippov     }
11317562658eSAlexander Filippov 
11327562658eSAlexander Filippov     struct group grp;
11337562658eSAlexander Filippov     struct group* grpPtr = nullptr;
11347562658eSAlexander Filippov     std::vector<char> buffer(buflen);
11357562658eSAlexander Filippov 
11367562658eSAlexander Filippov     auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
11377562658eSAlexander Filippov                              buffer.size(), &grpPtr);
11387562658eSAlexander Filippov 
11397562658eSAlexander Filippov     // Groups with a lot of members may require a buffer of bigger size than
11407562658eSAlexander Filippov     // suggested by _SC_GETGR_R_SIZE_MAX.
11417562658eSAlexander Filippov     // 32K should be enough for about 2K members.
11427562658eSAlexander Filippov     constexpr auto maxBufferLength = 32 * 1024;
11437562658eSAlexander Filippov     while (status == ERANGE && buflen < maxBufferLength)
11447562658eSAlexander Filippov     {
11457562658eSAlexander Filippov         buflen *= 2;
11467562658eSAlexander Filippov         buffer.resize(buflen);
11477562658eSAlexander Filippov 
114811ec666bSJiaqing Zhao         lg2::debug("Increase buffer for getgrnam_r() to {SIZE}", "SIZE",
114911ec666bSJiaqing Zhao                    buflen);
11507562658eSAlexander Filippov 
11517562658eSAlexander Filippov         status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
11527562658eSAlexander Filippov                             buffer.size(), &grpPtr);
11537562658eSAlexander Filippov     }
11547562658eSAlexander Filippov 
11557562658eSAlexander Filippov     // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
11567562658eSAlexander Filippov     // If no matching group record was found, these functions return 0
11577562658eSAlexander Filippov     // and store NULL in *grpPtr
11587562658eSAlexander Filippov     if (!status && (&grp == grpPtr))
11597562658eSAlexander Filippov     {
11607562658eSAlexander Filippov         if (primaryGid == grp.gr_gid)
11617562658eSAlexander Filippov         {
11627562658eSAlexander Filippov             return true;
11637562658eSAlexander Filippov         }
11647562658eSAlexander Filippov 
11657562658eSAlexander Filippov         for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
11667562658eSAlexander Filippov         {
11677562658eSAlexander Filippov             if (userName == grp.gr_mem[i])
11687562658eSAlexander Filippov             {
11697562658eSAlexander Filippov                 return true;
11707562658eSAlexander Filippov             }
11717562658eSAlexander Filippov         }
11727562658eSAlexander Filippov     }
11737562658eSAlexander Filippov     else if (status == ERANGE)
11747562658eSAlexander Filippov     {
117511ec666bSJiaqing Zhao         lg2::error("Group info of {GROUP} requires too much memory", "GROUP",
117611ec666bSJiaqing Zhao                    groupName);
11777562658eSAlexander Filippov     }
11787562658eSAlexander Filippov     else
11797562658eSAlexander Filippov     {
118011ec666bSJiaqing Zhao         lg2::error("Group {GROUP} does not exist", "GROUP", groupName);
11817562658eSAlexander Filippov     }
11827562658eSAlexander Filippov 
11837562658eSAlexander Filippov     return false;
11847562658eSAlexander Filippov }
11857562658eSAlexander Filippov 
executeGroupCreation(const char * groupName)1186da401fe5SNan Zhou void UserMgr::executeGroupCreation(const char* groupName)
1187da401fe5SNan Zhou {
1188da401fe5SNan Zhou     executeCmd("/usr/sbin/groupadd", groupName);
1189da401fe5SNan Zhou }
1190da401fe5SNan Zhou 
executeGroupDeletion(const char * groupName)1191da401fe5SNan Zhou void UserMgr::executeGroupDeletion(const char* groupName)
1192da401fe5SNan Zhou {
1193da401fe5SNan Zhou     executeCmd("/usr/sbin/groupdel", groupName);
1194da401fe5SNan Zhou }
1195da401fe5SNan Zhou 
getUserInfo(std::string userName)1196aeaf9413SRatan Gupta UserInfoMap UserMgr::getUserInfo(std::string userName)
1197aeaf9413SRatan Gupta {
1198aeaf9413SRatan Gupta     UserInfoMap userInfo;
1199aeaf9413SRatan Gupta     // Check whether the given user is local user or not.
12008a11d998SNan Zhou     if (isUserExist(userName))
1201aeaf9413SRatan Gupta     {
1202aeaf9413SRatan Gupta         const auto& user = usersList[userName];
1203aeaf9413SRatan Gupta         userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1204aeaf9413SRatan Gupta         userInfo.emplace("UserGroups", user.get()->userGroups());
1205aeaf9413SRatan Gupta         userInfo.emplace("UserEnabled", user.get()->userEnabled());
1206aeaf9413SRatan Gupta         userInfo.emplace("UserLockedForFailedAttempt",
1207aeaf9413SRatan Gupta                          user.get()->userLockedForFailedAttempt());
12083ab6cc28SJoseph Reynolds         userInfo.emplace("UserPasswordExpired",
12093ab6cc28SJoseph Reynolds                          user.get()->userPasswordExpired());
1210a1a754c2SAbhilash Raju         userInfo.emplace("TOTPSecretkeyRequired",
1211a1a754c2SAbhilash Raju                          user.get()->secretKeyGenerationRequired());
1212aeaf9413SRatan Gupta         userInfo.emplace("RemoteUser", false);
1213aeaf9413SRatan Gupta     }
1214aeaf9413SRatan Gupta     else
1215aeaf9413SRatan Gupta     {
12167562658eSAlexander Filippov         auto primaryGid = getPrimaryGroup(userName);
1217aeaf9413SRatan Gupta 
1218aeaf9413SRatan Gupta         DbusUserObj objects = getPrivilegeMapperObject();
1219aeaf9413SRatan Gupta 
12205fe724a7SRavi Teja         std::string ldapConfigPath;
1221745ce2edSJiaqing Zhao         std::string userPrivilege;
1222aeaf9413SRatan Gupta 
1223aeaf9413SRatan Gupta         try
1224aeaf9413SRatan Gupta         {
12257562658eSAlexander Filippov             for (const auto& [path, interfaces] : objects)
1226aeaf9413SRatan Gupta             {
12277562658eSAlexander Filippov                 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
12287562658eSAlexander Filippov                 if (it != interfaces.end())
1229aeaf9413SRatan Gupta                 {
12307562658eSAlexander Filippov                     auto propIt = it->second.find("Enabled");
12317562658eSAlexander Filippov                     if (propIt != it->second.end() &&
12327562658eSAlexander Filippov                         std::get<bool>(propIt->second))
12335fe724a7SRavi Teja                     {
12347562658eSAlexander Filippov                         ldapConfigPath = path.str + '/';
12355fe724a7SRavi Teja                         break;
12365fe724a7SRavi Teja                     }
12375fe724a7SRavi Teja                 }
12385fe724a7SRavi Teja             }
12395fe724a7SRavi Teja 
12405fe724a7SRavi Teja             if (ldapConfigPath.empty())
12415fe724a7SRavi Teja             {
12425fe724a7SRavi Teja                 return userInfo;
12435fe724a7SRavi Teja             }
12445fe724a7SRavi Teja 
12457562658eSAlexander Filippov             for (const auto& [path, interfaces] : objects)
12465fe724a7SRavi Teja             {
12477562658eSAlexander Filippov                 if (!path.str.starts_with(ldapConfigPath))
12485fe724a7SRavi Teja                 {
12497562658eSAlexander Filippov                     continue;
12507562658eSAlexander Filippov                 }
12517562658eSAlexander Filippov 
12527562658eSAlexander Filippov                 auto it = interfaces.find(
12537562658eSAlexander Filippov                     "xyz.openbmc_project.User.PrivilegeMapperEntry");
12547562658eSAlexander Filippov                 if (it != interfaces.end())
12555fe724a7SRavi Teja                 {
1256745ce2edSJiaqing Zhao                     std::string privilege;
1257745ce2edSJiaqing Zhao                     std::string groupName;
12585fe724a7SRavi Teja 
12597562658eSAlexander Filippov                     for (const auto& [propName, propValue] : it->second)
12605fe724a7SRavi Teja                     {
12617562658eSAlexander Filippov                         if (propName == "GroupName")
1262aeaf9413SRatan Gupta                         {
12637562658eSAlexander Filippov                             groupName = std::get<std::string>(propValue);
1264aeaf9413SRatan Gupta                         }
12657562658eSAlexander Filippov                         else if (propName == "Privilege")
1266aeaf9413SRatan Gupta                         {
12677562658eSAlexander Filippov                             privilege = std::get<std::string>(propValue);
1268aeaf9413SRatan Gupta                         }
1269745ce2edSJiaqing Zhao                     }
12707562658eSAlexander Filippov 
12717562658eSAlexander Filippov                     if (!groupName.empty() && !privilege.empty() &&
12727562658eSAlexander Filippov                         isGroupMember(userName, primaryGid, groupName))
1273aeaf9413SRatan Gupta                     {
1274745ce2edSJiaqing Zhao                         userPrivilege = privilege;
1275745ce2edSJiaqing Zhao                         break;
1276aeaf9413SRatan Gupta                     }
1277aeaf9413SRatan Gupta                 }
1278745ce2edSJiaqing Zhao                 if (!userPrivilege.empty())
1279745ce2edSJiaqing Zhao                 {
1280745ce2edSJiaqing Zhao                     break;
12815fe724a7SRavi Teja                 }
12825fe724a7SRavi Teja             }
12835fe724a7SRavi Teja 
128456862061SJiaqing Zhao             if (!userPrivilege.empty())
1285aeaf9413SRatan Gupta             {
1286745ce2edSJiaqing Zhao                 userInfo.emplace("UserPrivilege", userPrivilege);
1287aeaf9413SRatan Gupta             }
128856862061SJiaqing Zhao             else
128956862061SJiaqing Zhao             {
129011ec666bSJiaqing Zhao                 lg2::warning("LDAP group privilege mapping does not exist, "
129111ec666bSJiaqing Zhao                              "default \"priv-user\" is used");
129256862061SJiaqing Zhao                 userInfo.emplace("UserPrivilege", "priv-user");
129356862061SJiaqing Zhao             }
129456862061SJiaqing Zhao         }
1295aeaf9413SRatan Gupta         catch (const std::bad_variant_access& e)
1296aeaf9413SRatan Gupta         {
129711ec666bSJiaqing Zhao             lg2::error("Error while accessing variant: {ERR}", "ERR", e);
1298aeaf9413SRatan Gupta             elog<InternalFailure>();
1299aeaf9413SRatan Gupta         }
1300aeaf9413SRatan Gupta         userInfo.emplace("RemoteUser", true);
1301aeaf9413SRatan Gupta     }
1302aeaf9413SRatan Gupta 
1303aeaf9413SRatan Gupta     return userInfo;
1304aeaf9413SRatan Gupta }
1305aeaf9413SRatan Gupta 
initializeAccountPolicy()13064bc69810SNan Zhou void UserMgr::initializeAccountPolicy()
13079f630d9eSRichard Marian Thomaiyar {
13089164fd9bSRichard Marian Thomaiyar     std::string valueStr;
13099164fd9bSRichard Marian Thomaiyar     auto value = minPasswdLength;
13109164fd9bSRichard Marian Thomaiyar     unsigned long tmp = 0;
13112d042d14SJason M. Bills     if (getPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
13122d042d14SJason M. Bills                               valueStr) != success)
13139164fd9bSRichard Marian Thomaiyar     {
13149164fd9bSRichard Marian Thomaiyar         AccountPolicyIface::minPasswordLength(minPasswdLength);
13159164fd9bSRichard Marian Thomaiyar     }
13169164fd9bSRichard Marian Thomaiyar     else
13179164fd9bSRichard Marian Thomaiyar     {
13189164fd9bSRichard Marian Thomaiyar         try
13199164fd9bSRichard Marian Thomaiyar         {
13209164fd9bSRichard Marian Thomaiyar             tmp = std::stoul(valueStr, nullptr);
13219164fd9bSRichard Marian Thomaiyar             if (tmp > std::numeric_limits<decltype(value)>::max())
13229164fd9bSRichard Marian Thomaiyar             {
13239164fd9bSRichard Marian Thomaiyar                 throw std::out_of_range("Out of range");
13249164fd9bSRichard Marian Thomaiyar             }
13259164fd9bSRichard Marian Thomaiyar             value = static_cast<decltype(value)>(tmp);
13269164fd9bSRichard Marian Thomaiyar         }
13279164fd9bSRichard Marian Thomaiyar         catch (const std::exception& e)
13289164fd9bSRichard Marian Thomaiyar         {
132911ec666bSJiaqing Zhao             lg2::error("Exception for MinPasswordLength: {ERR}", "ERR", e);
1330045b1123SPatrick Venture             throw;
13319164fd9bSRichard Marian Thomaiyar         }
13329164fd9bSRichard Marian Thomaiyar         AccountPolicyIface::minPasswordLength(value);
13339164fd9bSRichard Marian Thomaiyar     }
13349164fd9bSRichard Marian Thomaiyar     valueStr.clear();
13353b280ec7SJason M. Bills     if (getPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
13363b280ec7SJason M. Bills                               valueStr) != success)
13379164fd9bSRichard Marian Thomaiyar     {
13389164fd9bSRichard Marian Thomaiyar         AccountPolicyIface::rememberOldPasswordTimes(0);
13399164fd9bSRichard Marian Thomaiyar     }
13409164fd9bSRichard Marian Thomaiyar     else
13419164fd9bSRichard Marian Thomaiyar     {
13429164fd9bSRichard Marian Thomaiyar         value = 0;
13439164fd9bSRichard Marian Thomaiyar         try
13449164fd9bSRichard Marian Thomaiyar         {
13459164fd9bSRichard Marian Thomaiyar             tmp = std::stoul(valueStr, nullptr);
13469164fd9bSRichard Marian Thomaiyar             if (tmp > std::numeric_limits<decltype(value)>::max())
13479164fd9bSRichard Marian Thomaiyar             {
13489164fd9bSRichard Marian Thomaiyar                 throw std::out_of_range("Out of range");
13499164fd9bSRichard Marian Thomaiyar             }
13509164fd9bSRichard Marian Thomaiyar             value = static_cast<decltype(value)>(tmp);
13519164fd9bSRichard Marian Thomaiyar         }
13529164fd9bSRichard Marian Thomaiyar         catch (const std::exception& e)
13539164fd9bSRichard Marian Thomaiyar         {
135411ec666bSJiaqing Zhao             lg2::error("Exception for RememberOldPasswordTimes: {ERR}", "ERR",
135511ec666bSJiaqing Zhao                        e);
1356045b1123SPatrick Venture             throw;
13579164fd9bSRichard Marian Thomaiyar         }
13589164fd9bSRichard Marian Thomaiyar         AccountPolicyIface::rememberOldPasswordTimes(value);
13599164fd9bSRichard Marian Thomaiyar     }
13609164fd9bSRichard Marian Thomaiyar     valueStr.clear();
13612d042d14SJason M. Bills     if (getPamModuleConfValue(faillockConfigFile, maxFailedAttempt, valueStr) !=
13622d042d14SJason M. Bills         success)
13639164fd9bSRichard Marian Thomaiyar     {
13649164fd9bSRichard Marian Thomaiyar         AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
13659164fd9bSRichard Marian Thomaiyar     }
13669164fd9bSRichard Marian Thomaiyar     else
13679164fd9bSRichard Marian Thomaiyar     {
13689164fd9bSRichard Marian Thomaiyar         uint16_t value16 = 0;
13699164fd9bSRichard Marian Thomaiyar         try
13709164fd9bSRichard Marian Thomaiyar         {
13719164fd9bSRichard Marian Thomaiyar             tmp = std::stoul(valueStr, nullptr);
13729164fd9bSRichard Marian Thomaiyar             if (tmp > std::numeric_limits<decltype(value16)>::max())
13739164fd9bSRichard Marian Thomaiyar             {
13749164fd9bSRichard Marian Thomaiyar                 throw std::out_of_range("Out of range");
13759164fd9bSRichard Marian Thomaiyar             }
13769164fd9bSRichard Marian Thomaiyar             value16 = static_cast<decltype(value16)>(tmp);
13779164fd9bSRichard Marian Thomaiyar         }
13789164fd9bSRichard Marian Thomaiyar         catch (const std::exception& e)
13799164fd9bSRichard Marian Thomaiyar         {
138011ec666bSJiaqing Zhao             lg2::error("Exception for MaxLoginAttemptBeforLockout: {ERR}",
138111ec666bSJiaqing Zhao                        "ERR", e);
1382045b1123SPatrick Venture             throw;
13839164fd9bSRichard Marian Thomaiyar         }
13849164fd9bSRichard Marian Thomaiyar         AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
13859164fd9bSRichard Marian Thomaiyar     }
13869164fd9bSRichard Marian Thomaiyar     valueStr.clear();
13872d042d14SJason M. Bills     if (getPamModuleConfValue(faillockConfigFile, unlockTimeout, valueStr) !=
13882d042d14SJason M. Bills         success)
13899164fd9bSRichard Marian Thomaiyar     {
13909164fd9bSRichard Marian Thomaiyar         AccountPolicyIface::accountUnlockTimeout(0);
13919164fd9bSRichard Marian Thomaiyar     }
13929164fd9bSRichard Marian Thomaiyar     else
13939164fd9bSRichard Marian Thomaiyar     {
13949164fd9bSRichard Marian Thomaiyar         uint32_t value32 = 0;
13959164fd9bSRichard Marian Thomaiyar         try
13969164fd9bSRichard Marian Thomaiyar         {
13979164fd9bSRichard Marian Thomaiyar             tmp = std::stoul(valueStr, nullptr);
13989164fd9bSRichard Marian Thomaiyar             if (tmp > std::numeric_limits<decltype(value32)>::max())
13999164fd9bSRichard Marian Thomaiyar             {
14009164fd9bSRichard Marian Thomaiyar                 throw std::out_of_range("Out of range");
14019164fd9bSRichard Marian Thomaiyar             }
14029164fd9bSRichard Marian Thomaiyar             value32 = static_cast<decltype(value32)>(tmp);
14039164fd9bSRichard Marian Thomaiyar         }
14049164fd9bSRichard Marian Thomaiyar         catch (const std::exception& e)
14059164fd9bSRichard Marian Thomaiyar         {
140611ec666bSJiaqing Zhao             lg2::error("Exception for AccountUnlockTimeout: {ERR}", "ERR", e);
1407045b1123SPatrick Venture             throw;
14089164fd9bSRichard Marian Thomaiyar         }
14099164fd9bSRichard Marian Thomaiyar         AccountPolicyIface::accountUnlockTimeout(value32);
14109164fd9bSRichard Marian Thomaiyar     }
14114bc69810SNan Zhou }
14124bc69810SNan Zhou 
initUserObjects(void)14134bc69810SNan Zhou void UserMgr::initUserObjects(void)
14144bc69810SNan Zhou {
14154bc69810SNan Zhou     // All user management lock has to be based on /etc/shadow
14164bc69810SNan Zhou     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
14174bc69810SNan Zhou     std::vector<std::string> userNameList;
14184bc69810SNan Zhou     std::vector<std::string> sshGrpUsersList;
14194bc69810SNan Zhou     UserSSHLists userSSHLists = getUserAndSshGrpList();
14204bc69810SNan Zhou     userNameList = std::move(userSSHLists.first);
14214bc69810SNan Zhou     sshGrpUsersList = std::move(userSSHLists.second);
14224bc69810SNan Zhou 
14234bc69810SNan Zhou     if (!userNameList.empty())
14244bc69810SNan Zhou     {
14254bc69810SNan Zhou         std::map<std::string, std::vector<std::string>> groupLists;
1426da401fe5SNan Zhou         // We only track users that are in the |predefinedGroups|
1427da401fe5SNan Zhou         // The other groups don't contain real BMC users.
1428da401fe5SNan Zhou         for (const char* grp : predefinedGroups)
14294bc69810SNan Zhou         {
14304bc69810SNan Zhou             if (grp == grpSsh)
14314bc69810SNan Zhou             {
14324bc69810SNan Zhou                 groupLists.emplace(grp, sshGrpUsersList);
14334bc69810SNan Zhou             }
14344bc69810SNan Zhou             else
14354bc69810SNan Zhou             {
14364bc69810SNan Zhou                 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
14374bc69810SNan Zhou                 groupLists.emplace(grp, grpUsersList);
14384bc69810SNan Zhou             }
14394bc69810SNan Zhou         }
14404bc69810SNan Zhou         for (auto& grp : privMgr)
14414bc69810SNan Zhou         {
14424bc69810SNan Zhou             std::vector<std::string> grpUsersList = getUsersInGroup(grp);
14434bc69810SNan Zhou             groupLists.emplace(grp, grpUsersList);
14444bc69810SNan Zhou         }
14454bc69810SNan Zhou 
14464bc69810SNan Zhou         for (auto& user : userNameList)
14474bc69810SNan Zhou         {
14484bc69810SNan Zhou             std::vector<std::string> userGroups;
14494bc69810SNan Zhou             std::string userPriv;
14504bc69810SNan Zhou             for (const auto& grp : groupLists)
14514bc69810SNan Zhou             {
14524bc69810SNan Zhou                 std::vector<std::string> tempGrp = grp.second;
14534bc69810SNan Zhou                 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
14544bc69810SNan Zhou                     tempGrp.end())
14554bc69810SNan Zhou                 {
14564bc69810SNan Zhou                     if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
14574bc69810SNan Zhou                         privMgr.end())
14584bc69810SNan Zhou                     {
14594bc69810SNan Zhou                         userPriv = grp.first;
14604bc69810SNan Zhou                     }
14614bc69810SNan Zhou                     else
14624bc69810SNan Zhou                     {
14634bc69810SNan Zhou                         userGroups.emplace_back(grp.first);
14644bc69810SNan Zhou                     }
14654bc69810SNan Zhou                 }
14664bc69810SNan Zhou             }
14674bc69810SNan Zhou             // Add user objects to the Users path.
14684bc69810SNan Zhou             sdbusplus::message::object_path tempObjPath(usersObjPath);
14694bc69810SNan Zhou             tempObjPath /= user;
14704bc69810SNan Zhou             std::string objPath(tempObjPath);
14714bc69810SNan Zhou             std::sort(userGroups.begin(), userGroups.end());
14724bc69810SNan Zhou             usersList.emplace(user, std::make_unique<phosphor::user::Users>(
14734bc69810SNan Zhou                                         bus, objPath.c_str(), userGroups,
14744bc69810SNan Zhou                                         userPriv, isUserEnabled(user), *this));
14754bc69810SNan Zhou         }
14764bc69810SNan Zhou     }
14774bc69810SNan Zhou }
14784bc69810SNan Zhou 
load()1479*93804ebaSAbhilash Raju void UserMgr::load()
1480*93804ebaSAbhilash Raju {
1481*93804ebaSAbhilash Raju     std::optional<std::string> authTypeStr;
1482*93804ebaSAbhilash Raju     if (std::filesystem::exists(mfaConfPath))
1483*93804ebaSAbhilash Raju     {
1484*93804ebaSAbhilash Raju         serializer.load();
1485*93804ebaSAbhilash Raju         serializer.deserialize("authtype", authTypeStr);
1486*93804ebaSAbhilash Raju     }
1487*93804ebaSAbhilash Raju     auto authType =
1488*93804ebaSAbhilash Raju         authTypeStr.transform(MultiFactorAuthConfiguration::convertStringToType)
1489*93804ebaSAbhilash Raju             .value_or(std::optional(MultiFactorAuthType::None));
1490*93804ebaSAbhilash Raju     if (authType)
1491*93804ebaSAbhilash Raju     {
1492*93804ebaSAbhilash Raju         enabled(*authType, true);
1493*93804ebaSAbhilash Raju     }
1494*93804ebaSAbhilash Raju }
1495*93804ebaSAbhilash Raju 
UserMgr(sdbusplus::bus_t & bus,const char * path)14964bc69810SNan Zhou UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
14974bc69810SNan Zhou     Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1498*93804ebaSAbhilash Raju     serializer(mfaConfPath), faillockConfigFile(defaultFaillockConfigFile),
14993b280ec7SJason M. Bills     pwHistoryConfigFile(defaultPWHistoryConfigFile),
15002d042d14SJason M. Bills     pwQualityConfigFile(defaultPWQualityConfigFile)
1501*93804ebaSAbhilash Raju 
15024bc69810SNan Zhou {
15034bc69810SNan Zhou     UserMgrIface::allPrivileges(privMgr);
1504da401fe5SNan Zhou     groupsMgr = readAllGroupsOnSystem();
15054bc69810SNan Zhou     std::sort(groupsMgr.begin(), groupsMgr.end());
15064bc69810SNan Zhou     UserMgrIface::allGroups(groupsMgr);
15074bc69810SNan Zhou     initializeAccountPolicy();
1508*93804ebaSAbhilash Raju     load();
15099f630d9eSRichard Marian Thomaiyar     initUserObjects();
15101af12233SRatan Gupta     // emit the signal
15111af12233SRatan Gupta     this->emit_object_added();
15129f630d9eSRichard Marian Thomaiyar }
15139f630d9eSRichard Marian Thomaiyar 
executeUserAdd(const char * userName,const char * groups,bool sshRequested,bool enabled)151449c81364SNan Zhou void UserMgr::executeUserAdd(const char* userName, const char* groups,
151549c81364SNan Zhou                              bool sshRequested, bool enabled)
151649c81364SNan Zhou {
151749c81364SNan Zhou     // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
151849c81364SNan Zhou     // 1970-01-01, that's an implementation-defined behavior
151949c81364SNan Zhou     executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
152075ea3e47STang Yiwei                (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
152149c81364SNan Zhou                (enabled ? "" : "1970-01-01"));
152249c81364SNan Zhou }
152349c81364SNan Zhou 
executeUserDelete(const char * userName)152449c81364SNan Zhou void UserMgr::executeUserDelete(const char* userName)
152549c81364SNan Zhou {
152649c81364SNan Zhou     executeCmd("/usr/sbin/userdel", userName, "-r");
152749c81364SNan Zhou }
152849c81364SNan Zhou 
executeUserClearFailRecords(const char * userName)1529ac921a5eSJayanth Othayoth void UserMgr::executeUserClearFailRecords(const char* userName)
1530ac921a5eSJayanth Othayoth {
1531ac921a5eSJayanth Othayoth     executeCmd("/usr/sbin/faillock", "--user", userName, "--reset");
1532ac921a5eSJayanth Othayoth }
1533ac921a5eSJayanth Othayoth 
executeUserRename(const char * userName,const char * newUserName)1534f25443e8SNan Zhou void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1535f25443e8SNan Zhou {
1536f25443e8SNan Zhou     std::string newHomeDir = "/home/";
1537f25443e8SNan Zhou     newHomeDir += newUserName;
1538f25443e8SNan Zhou     executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1539f25443e8SNan Zhou                newHomeDir.c_str(), "-m");
1540f25443e8SNan Zhou }
1541f25443e8SNan Zhou 
executeUserModify(const char * userName,const char * newGroups,bool sshRequested)1542fef63038SNan Zhou void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1543fef63038SNan Zhou                                 bool sshRequested)
1544fef63038SNan Zhou {
1545fef63038SNan Zhou     executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
154675ea3e47STang Yiwei                (sshRequested ? "/bin/sh" : "/sbin/nologin"));
1547fef63038SNan Zhou }
1548fef63038SNan Zhou 
executeUserModifyUserEnable(const char * userName,bool enabled)15496b6f2d80SNan Zhou void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
15506b6f2d80SNan Zhou {
15516b6f2d80SNan Zhou     // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
15526b6f2d80SNan Zhou     // 1970-01-01, that's an implementation-defined behavior
15536b6f2d80SNan Zhou     executeCmd("/usr/sbin/usermod", userName, "-e",
15546b6f2d80SNan Zhou                (enabled ? "" : "1970-01-01"));
15556b6f2d80SNan Zhou }
15566b6f2d80SNan Zhou 
getFailedAttempt(const char * userName)1557a295303bSNan Zhou std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1558a295303bSNan Zhou {
15592d042d14SJason M. Bills     return executeCmd("/usr/sbin/faillock", "--user", userName);
1560a295303bSNan Zhou }
1561a295303bSNan Zhou 
enabled(MultiFactorAuthType value,bool skipSignal)1562a1a754c2SAbhilash Raju MultiFactorAuthType UserMgr::enabled(MultiFactorAuthType value, bool skipSignal)
1563a1a754c2SAbhilash Raju {
1564a1a754c2SAbhilash Raju     if (value == enabled())
1565a1a754c2SAbhilash Raju     {
1566a1a754c2SAbhilash Raju         return value;
1567a1a754c2SAbhilash Raju     }
1568a1a754c2SAbhilash Raju     switch (value)
1569a1a754c2SAbhilash Raju     {
1570a1a754c2SAbhilash Raju         case MultiFactorAuthType::None:
1571a1a754c2SAbhilash Raju             for (auto type : {MultiFactorAuthType::GoogleAuthenticator})
1572a1a754c2SAbhilash Raju             {
1573a1a754c2SAbhilash Raju                 for (auto& u : usersList)
1574a1a754c2SAbhilash Raju                 {
1575a1a754c2SAbhilash Raju                     u.second->enableMultiFactorAuth(type, false);
1576a1a754c2SAbhilash Raju                 }
1577a1a754c2SAbhilash Raju             }
1578a1a754c2SAbhilash Raju             break;
1579a1a754c2SAbhilash Raju         default:
1580a1a754c2SAbhilash Raju             for (auto& u : usersList)
1581a1a754c2SAbhilash Raju             {
1582a1a754c2SAbhilash Raju                 u.second->enableMultiFactorAuth(value, true);
1583a1a754c2SAbhilash Raju             }
1584a1a754c2SAbhilash Raju             break;
1585a1a754c2SAbhilash Raju     }
1586*93804ebaSAbhilash Raju     serializer.serialize(
1587*93804ebaSAbhilash Raju         "authtype", MultiFactorAuthConfiguration::convertTypeToString(value));
1588*93804ebaSAbhilash Raju     serializer.store();
1589a1a754c2SAbhilash Raju     return MultiFactorAuthConfigurationIface::enabled(value, skipSignal);
1590a1a754c2SAbhilash Raju }
secretKeyRequired(std::string userName)1591a1a754c2SAbhilash Raju bool UserMgr::secretKeyRequired(std::string userName)
1592a1a754c2SAbhilash Raju {
1593a1a754c2SAbhilash Raju     if (usersList.contains(userName))
1594a1a754c2SAbhilash Raju     {
1595a1a754c2SAbhilash Raju         return usersList[userName]->secretKeyGenerationRequired();
1596a1a754c2SAbhilash Raju     }
1597a1a754c2SAbhilash Raju     return false;
1598a1a754c2SAbhilash Raju }
15999f630d9eSRichard Marian Thomaiyar } // namespace user
16009f630d9eSRichard Marian Thomaiyar } // namespace phosphor
1601