xref: /openbmc/phosphor-user-manager/user_mgr.cpp (revision 5686206103721b8cc452179b4947ad67e3571bcf)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "config.h"
18 
19 #include "user_mgr.hpp"
20 
21 #include "file.hpp"
22 #include "shadowlock.hpp"
23 #include "users.hpp"
24 
25 #include <grp.h>
26 #include <pwd.h>
27 #include <shadow.h>
28 #include <sys/types.h>
29 #include <sys/wait.h>
30 #include <time.h>
31 #include <unistd.h>
32 
33 #include <boost/algorithm/string/split.hpp>
34 #include <phosphor-logging/elog-errors.hpp>
35 #include <phosphor-logging/elog.hpp>
36 #include <phosphor-logging/log.hpp>
37 #include <xyz/openbmc_project/Common/error.hpp>
38 #include <xyz/openbmc_project/User/Common/error.hpp>
39 
40 #include <algorithm>
41 #include <array>
42 #include <ctime>
43 #include <fstream>
44 #include <numeric>
45 #include <regex>
46 #include <span>
47 #include <string>
48 #include <string_view>
49 #include <vector>
50 
51 namespace phosphor
52 {
53 namespace user
54 {
55 
56 static constexpr const char* passwdFileName = "/etc/passwd";
57 static constexpr size_t ipmiMaxUserNameLen = 16;
58 static constexpr size_t systemMaxUserNameLen = 30;
59 static constexpr const char* grpSsh = "ssh";
60 static constexpr int success = 0;
61 static constexpr int failure = -1;
62 
63 // pam modules related
64 static constexpr const char* pamTally2 = "pam_tally2.so";
65 static constexpr const char* pamCrackLib = "pam_cracklib.so";
66 static constexpr const char* pamPWHistory = "pam_pwhistory.so";
67 static constexpr const char* minPasswdLenProp = "minlen";
68 static constexpr const char* remOldPasswdCount = "remember";
69 static constexpr const char* maxFailedAttempt = "deny";
70 static constexpr const char* unlockTimeout = "unlock_time";
71 static constexpr const char* defaultPamPasswdConfigFile =
72     "/etc/pam.d/common-password";
73 static constexpr const char* defaultPamAuthConfigFile =
74     "/etc/pam.d/common-auth";
75 
76 // Object Manager related
77 static constexpr const char* ldapMgrObjBasePath =
78     "/xyz/openbmc_project/user/ldap";
79 
80 // Object Mapper related
81 static constexpr const char* objMapperService =
82     "xyz.openbmc_project.ObjectMapper";
83 static constexpr const char* objMapperPath =
84     "/xyz/openbmc_project/object_mapper";
85 static constexpr const char* objMapperInterface =
86     "xyz.openbmc_project.ObjectMapper";
87 
88 using namespace phosphor::logging;
89 using InsufficientPermission =
90     sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
91 using InternalFailure =
92     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
93 using InvalidArgument =
94     sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
95 using UserNameExists =
96     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
97 using UserNameDoesNotExist =
98     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
99 using UserNameGroupFail =
100     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
101 using NoResource =
102     sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
103 using Argument = xyz::openbmc_project::Common::InvalidArgument;
104 using GroupNameExists =
105     sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameExists;
106 using GroupNameDoesNotExists =
107     sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameDoesNotExist;
108 
109 namespace
110 {
111 
112 // The hardcoded groups in OpenBMC projects
113 constexpr std::array<const char*, 4> predefinedGroups = {"web", "redfish",
114                                                          "ipmi", "ssh"};
115 
116 // These prefixes are for Dynamic Redfish authorization. See
117 // https://github.com/openbmc/docs/blob/master/designs/redfish-authorization.md
118 
119 // Base role and base privileges are added by Redfish implementation (e.g.,
120 // BMCWeb) at compile time
121 constexpr std::array<const char*, 4> allowedGroupPrefix = {
122     "openbmc_rfr_",  // OpenBMC Redfish Base Role
123     "openbmc_rfp_",  // OpenBMC Redfish Base Privileges
124     "openbmc_orfr_", // OpenBMC Redfish OEM Role
125     "openbmc_orfp_", // OpenBMC Redfish OEM Privileges
126 };
127 
128 void checkAndThrowsForGroupChangeAllowed(const std::string& groupName)
129 {
130     bool allowed = false;
131     for (std::string_view prefix : allowedGroupPrefix)
132     {
133         if (groupName.starts_with(prefix))
134         {
135             allowed = true;
136             break;
137         }
138     }
139     if (!allowed)
140     {
141         log<level::ERR>("Invalid group name; not in the allowed list",
142                         entry("GroupName=%s", groupName.c_str()));
143         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
144                               Argument::ARGUMENT_VALUE(groupName.c_str()));
145     }
146 }
147 
148 } // namespace
149 
150 std::string getCSVFromVector(std::span<const std::string> vec)
151 {
152     if (vec.empty())
153     {
154         return "";
155     }
156     return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
157                            [](std::string&& val, std::string_view element) {
158                                val += ',';
159                                val += element;
160                                return val;
161                            });
162 }
163 
164 bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
165 {
166     std::string::size_type delStrPos = csvStr.find(delStr);
167     if (delStrPos != std::string::npos)
168     {
169         // need to also delete the comma char
170         if (delStrPos == 0)
171         {
172             csvStr.erase(delStrPos, delStr.size() + 1);
173         }
174         else
175         {
176             csvStr.erase(delStrPos - 1, delStr.size() + 1);
177         }
178         return true;
179     }
180     return false;
181 }
182 
183 bool UserMgr::isUserExist(const std::string& userName)
184 {
185     if (userName.empty())
186     {
187         log<level::ERR>("User name is empty");
188         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
189                               Argument::ARGUMENT_VALUE("Null"));
190     }
191     if (usersList.find(userName) == usersList.end())
192     {
193         return false;
194     }
195     return true;
196 }
197 
198 void UserMgr::throwForUserDoesNotExist(const std::string& userName)
199 {
200     if (!isUserExist(userName))
201     {
202         log<level::ERR>("User does not exist",
203                         entry("USER_NAME=%s", userName.c_str()));
204         elog<UserNameDoesNotExist>();
205     }
206 }
207 
208 void UserMgr::checkAndThrowForDisallowedGroupCreation(
209     const std::string& groupName)
210 {
211     if (groupName.size() > maxSystemGroupNameLength ||
212         !std::regex_match(groupName.c_str(),
213                           std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
214     {
215         log<level::ERR>("Invalid group name",
216                         entry("GroupName=%s", groupName.c_str()));
217         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
218                               Argument::ARGUMENT_VALUE(groupName.c_str()));
219     }
220     checkAndThrowsForGroupChangeAllowed(groupName);
221 }
222 
223 void UserMgr::throwForUserExists(const std::string& userName)
224 {
225     if (isUserExist(userName))
226     {
227         log<level::ERR>("User already exists",
228                         entry("USER_NAME=%s", userName.c_str()));
229         elog<UserNameExists>();
230     }
231 }
232 
233 void UserMgr::throwForUserNameConstraints(
234     const std::string& userName, const std::vector<std::string>& groupNames)
235 {
236     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
237         groupNames.end())
238     {
239         if (userName.length() > ipmiMaxUserNameLen)
240         {
241             log<level::ERR>("IPMI user name length limitation",
242                             entry("SIZE=%d", userName.length()));
243             elog<UserNameGroupFail>(
244                 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
245                     "IPMI length"));
246         }
247     }
248     if (userName.length() > systemMaxUserNameLen)
249     {
250         log<level::ERR>("User name length limitation",
251                         entry("SIZE=%d", userName.length()));
252         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
253                               Argument::ARGUMENT_VALUE("Invalid length"));
254     }
255     if (!std::regex_match(userName.c_str(),
256                           std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
257     {
258         log<level::ERR>("Invalid user name",
259                         entry("USER_NAME=%s", userName.c_str()));
260         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
261                               Argument::ARGUMENT_VALUE("Invalid data"));
262     }
263 }
264 
265 void UserMgr::throwForMaxGrpUserCount(
266     const std::vector<std::string>& groupNames)
267 {
268     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
269         groupNames.end())
270     {
271         if (getIpmiUsersCount() >= ipmiMaxUsers)
272         {
273             log<level::ERR>("IPMI user limit reached");
274             elog<NoResource>(
275                 xyz::openbmc_project::User::Common::NoResource::REASON(
276                     "ipmi user count reached"));
277         }
278     }
279     else
280     {
281         if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
282                                         (maxSystemUsers - ipmiMaxUsers))
283         {
284             log<level::ERR>("Non-ipmi User limit reached");
285             elog<NoResource>(
286                 xyz::openbmc_project::User::Common::NoResource::REASON(
287                     "Non-ipmi user count reached"));
288         }
289     }
290     return;
291 }
292 
293 void UserMgr::throwForInvalidPrivilege(const std::string& priv)
294 {
295     if (!priv.empty() &&
296         (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
297     {
298         log<level::ERR>("Invalid privilege");
299         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
300                               Argument::ARGUMENT_VALUE(priv.c_str()));
301     }
302 }
303 
304 void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
305 {
306     for (auto& group : groupNames)
307     {
308         if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
309             groupsMgr.end())
310         {
311             log<level::ERR>("Invalid Group Name listed");
312             elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
313                                   Argument::ARGUMENT_VALUE(group.c_str()));
314         }
315     }
316 }
317 
318 std::vector<std::string> UserMgr::readAllGroupsOnSystem()
319 {
320     std::vector<std::string> allGroups = {predefinedGroups.begin(),
321                                           predefinedGroups.end()};
322     // rewinds to the beginning of the group database
323     setgrent();
324     struct group* gr = getgrent();
325     while (gr != nullptr)
326     {
327         std::string group(gr->gr_name);
328         for (std::string_view prefix : allowedGroupPrefix)
329         {
330             if (group.starts_with(prefix))
331             {
332                 allGroups.push_back(gr->gr_name);
333             }
334         }
335         gr = getgrent();
336     }
337     // close the group database
338     endgrent();
339     return allGroups;
340 }
341 
342 void UserMgr::createUser(std::string userName,
343                          std::vector<std::string> groupNames, std::string priv,
344                          bool enabled)
345 {
346     throwForInvalidPrivilege(priv);
347     throwForInvalidGroups(groupNames);
348     // All user management lock has to be based on /etc/shadow
349     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
350     throwForUserExists(userName);
351     throwForUserNameConstraints(userName, groupNames);
352     throwForMaxGrpUserCount(groupNames);
353 
354     std::string groups = getCSVFromVector(groupNames);
355     bool sshRequested = removeStringFromCSV(groups, grpSsh);
356 
357     // treat privilege as a group - This is to avoid using different file to
358     // store the same.
359     if (!priv.empty())
360     {
361         if (groups.size() != 0)
362         {
363             groups += ",";
364         }
365         groups += priv;
366     }
367     try
368     {
369         executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
370     }
371     catch (const InternalFailure& e)
372     {
373         log<level::ERR>("Unable to create new user");
374         elog<InternalFailure>();
375     }
376 
377     // Add the users object before sending out the signal
378     sdbusplus::message::object_path tempObjPath(usersObjPath);
379     tempObjPath /= userName;
380     std::string userObj(tempObjPath);
381     std::sort(groupNames.begin(), groupNames.end());
382     usersList.emplace(
383         userName, std::make_unique<phosphor::user::Users>(
384                       bus, userObj.c_str(), groupNames, priv, enabled, *this));
385 
386     log<level::INFO>("User created successfully",
387                      entry("USER_NAME=%s", userName.c_str()));
388     return;
389 }
390 
391 void UserMgr::deleteUser(std::string userName)
392 {
393     // All user management lock has to be based on /etc/shadow
394     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
395     throwForUserDoesNotExist(userName);
396     try
397     {
398         executeUserDelete(userName.c_str());
399     }
400     catch (const InternalFailure& e)
401     {
402         log<level::ERR>("User delete failed",
403                         entry("USER_NAME=%s", userName.c_str()));
404         elog<InternalFailure>();
405     }
406 
407     usersList.erase(userName);
408 
409     log<level::INFO>("User deleted successfully",
410                      entry("USER_NAME=%s", userName.c_str()));
411     return;
412 }
413 
414 void UserMgr::checkDeleteGroupConstraints(const std::string& groupName)
415 {
416     if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) ==
417         groupsMgr.end())
418     {
419         log<level::ERR>("Group already exists",
420                         entry("GROUP_NAME=%s", groupName.c_str()));
421         elog<GroupNameDoesNotExists>();
422     }
423     checkAndThrowsForGroupChangeAllowed(groupName);
424 }
425 
426 void UserMgr::deleteGroup(std::string groupName)
427 {
428     checkDeleteGroupConstraints(groupName);
429     try
430     {
431         executeGroupDeletion(groupName.c_str());
432     }
433     catch (const InternalFailure& e)
434     {
435         log<level::ERR>("Group delete failed",
436                         entry("GROUP_NAME=%s", groupName.c_str()));
437         elog<InternalFailure>();
438     }
439 
440     groupsMgr.erase(std::find(groupsMgr.begin(), groupsMgr.end(), groupName));
441     UserMgrIface::allGroups(groupsMgr);
442     log<level::INFO>("Group deleted successfully",
443                      entry("GROUP_NAME=%s", groupName.c_str()));
444 }
445 
446 void UserMgr::checkCreateGroupConstraints(const std::string& groupName)
447 {
448     if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) !=
449         groupsMgr.end())
450     {
451         log<level::ERR>("Group already exists",
452                         entry("GROUP_NAME=%s", groupName.c_str()));
453         elog<GroupNameExists>();
454     }
455     checkAndThrowForDisallowedGroupCreation(groupName);
456     if (groupsMgr.size() >= maxSystemGroupCount)
457     {
458         log<level::ERR>("Group limit reached");
459         elog<NoResource>(xyz::openbmc_project::User::Common::NoResource::REASON(
460             "Group limit reached"));
461     }
462 }
463 
464 void UserMgr::createGroup(std::string groupName)
465 {
466     checkCreateGroupConstraints(groupName);
467     try
468     {
469         executeGroupCreation(groupName.c_str());
470     }
471     catch (const InternalFailure& e)
472     {
473         log<level::ERR>("Group create failed",
474                         entry("GROUP_NAME=%s", groupName.c_str()));
475         elog<InternalFailure>();
476     }
477     groupsMgr.push_back(groupName);
478     UserMgrIface::allGroups(groupsMgr);
479 }
480 
481 void UserMgr::renameUser(std::string userName, std::string newUserName)
482 {
483     // All user management lock has to be based on /etc/shadow
484     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
485     throwForUserDoesNotExist(userName);
486     throwForUserExists(newUserName);
487     throwForUserNameConstraints(newUserName,
488                                 usersList[userName].get()->userGroups());
489     try
490     {
491         executeUserRename(userName.c_str(), newUserName.c_str());
492     }
493     catch (const InternalFailure& e)
494     {
495         log<level::ERR>("User rename failed",
496                         entry("USER_NAME=%s", userName.c_str()));
497         elog<InternalFailure>();
498     }
499     const auto& user = usersList[userName];
500     std::string priv = user.get()->userPrivilege();
501     std::vector<std::string> groupNames = user.get()->userGroups();
502     bool enabled = user.get()->userEnabled();
503     sdbusplus::message::object_path tempObjPath(usersObjPath);
504     tempObjPath /= newUserName;
505     std::string newUserObj(tempObjPath);
506     // Special group 'ipmi' needs a way to identify user renamed, in order to
507     // update encrypted password. It can't rely only on InterfacesRemoved &
508     // InterfacesAdded. So first send out userRenamed signal.
509     this->userRenamed(userName, newUserName);
510     usersList.erase(userName);
511     usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
512                                        bus, newUserObj.c_str(), groupNames,
513                                        priv, enabled, *this));
514     return;
515 }
516 
517 void UserMgr::updateGroupsAndPriv(const std::string& userName,
518                                   std::vector<std::string> groupNames,
519                                   const std::string& priv)
520 {
521     throwForInvalidPrivilege(priv);
522     throwForInvalidGroups(groupNames);
523     // All user management lock has to be based on /etc/shadow
524     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
525     throwForUserDoesNotExist(userName);
526     const std::vector<std::string>& oldGroupNames =
527         usersList[userName].get()->userGroups();
528     std::vector<std::string> groupDiff;
529     // Note: already dealing with sorted group lists.
530     std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
531                                   groupNames.begin(), groupNames.end(),
532                                   std::back_inserter(groupDiff));
533     if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
534         groupDiff.end())
535     {
536         throwForUserNameConstraints(userName, groupNames);
537         throwForMaxGrpUserCount(groupNames);
538     }
539 
540     std::string groups = getCSVFromVector(groupNames);
541     bool sshRequested = removeStringFromCSV(groups, grpSsh);
542 
543     // treat privilege as a group - This is to avoid using different file to
544     // store the same.
545     if (!priv.empty())
546     {
547         if (groups.size() != 0)
548         {
549             groups += ",";
550         }
551         groups += priv;
552     }
553     try
554     {
555         executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
556     }
557     catch (const InternalFailure& e)
558     {
559         log<level::ERR>("Unable to modify user privilege / groups");
560         elog<InternalFailure>();
561     }
562 
563     log<level::INFO>("User groups / privilege updated successfully",
564                      entry("USER_NAME=%s", userName.c_str()));
565     std::sort(groupNames.begin(), groupNames.end());
566     usersList[userName]->setUserGroups(groupNames);
567     usersList[userName]->setUserPrivilege(priv);
568     return;
569 }
570 
571 uint8_t UserMgr::minPasswordLength(uint8_t value)
572 {
573     if (value == AccountPolicyIface::minPasswordLength())
574     {
575         return value;
576     }
577     if (value < minPasswdLength)
578     {
579         log<level::ERR>(("Attempting to set minPasswordLength to less than " +
580                          std::to_string(minPasswdLength))
581                             .c_str(),
582                         entry("SIZE=%d", value));
583         elog<InvalidArgument>(
584             Argument::ARGUMENT_NAME("minPasswordLength"),
585             Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
586     }
587     if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
588                              std::to_string(value)) != success)
589     {
590         log<level::ERR>("Unable to set minPasswordLength");
591         elog<InternalFailure>();
592     }
593     return AccountPolicyIface::minPasswordLength(value);
594 }
595 
596 uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
597 {
598     if (value == AccountPolicyIface::rememberOldPasswordTimes())
599     {
600         return value;
601     }
602     if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
603                              std::to_string(value)) != success)
604     {
605         log<level::ERR>("Unable to set rememberOldPasswordTimes");
606         elog<InternalFailure>();
607     }
608     return AccountPolicyIface::rememberOldPasswordTimes(value);
609 }
610 
611 uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
612 {
613     if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
614     {
615         return value;
616     }
617     if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
618                              std::to_string(value)) != success)
619     {
620         log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
621         elog<InternalFailure>();
622     }
623     return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
624 }
625 
626 uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
627 {
628     if (value == AccountPolicyIface::accountUnlockTimeout())
629     {
630         return value;
631     }
632     if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
633         success)
634     {
635         log<level::ERR>("Unable to set accountUnlockTimeout");
636         elog<InternalFailure>();
637     }
638     return AccountPolicyIface::accountUnlockTimeout(value);
639 }
640 
641 int UserMgr::getPamModuleArgValue(const std::string& moduleName,
642                                   const std::string& argName,
643                                   std::string& argValue)
644 {
645     std::string fileName;
646     if (moduleName == pamTally2)
647     {
648         fileName = pamAuthConfigFile;
649     }
650     else
651     {
652         fileName = pamPasswdConfigFile;
653     }
654     std::ifstream fileToRead(fileName, std::ios::in);
655     if (!fileToRead.is_open())
656     {
657         log<level::ERR>("Failed to open pam configuration file",
658                         entry("FILE_NAME=%s", fileName.c_str()));
659         return failure;
660     }
661     std::string line;
662     auto argSearch = argName + "=";
663     size_t startPos = 0;
664     size_t endPos = 0;
665     while (getline(fileToRead, line))
666     {
667         // skip comments section starting with #
668         if ((startPos = line.find('#')) != std::string::npos)
669         {
670             if (startPos == 0)
671             {
672                 continue;
673             }
674             // skip comments after meaningful section and process those
675             line = line.substr(0, startPos);
676         }
677         if (line.find(moduleName) != std::string::npos)
678         {
679             if ((startPos = line.find(argSearch)) != std::string::npos)
680             {
681                 if ((endPos = line.find(' ', startPos)) == std::string::npos)
682                 {
683                     endPos = line.size();
684                 }
685                 startPos += argSearch.size();
686                 argValue = line.substr(startPos, endPos - startPos);
687                 return success;
688             }
689         }
690     }
691     return failure;
692 }
693 
694 int UserMgr::setPamModuleArgValue(const std::string& moduleName,
695                                   const std::string& argName,
696                                   const std::string& argValue)
697 {
698     std::string fileName;
699     if (moduleName == pamTally2)
700     {
701         fileName = pamAuthConfigFile;
702     }
703     else
704     {
705         fileName = pamPasswdConfigFile;
706     }
707     std::string tmpFileName = fileName + "_tmp";
708     std::ifstream fileToRead(fileName, std::ios::in);
709     std::ofstream fileToWrite(tmpFileName, std::ios::out);
710     if (!fileToRead.is_open() || !fileToWrite.is_open())
711     {
712         log<level::ERR>("Failed to open pam configuration /tmp file",
713                         entry("FILE_NAME=%s", fileName.c_str()));
714         return failure;
715     }
716     std::string line;
717     auto argSearch = argName + "=";
718     size_t startPos = 0;
719     size_t endPos = 0;
720     bool found = false;
721     while (getline(fileToRead, line))
722     {
723         // skip comments section starting with #
724         if ((startPos = line.find('#')) != std::string::npos)
725         {
726             if (startPos == 0)
727             {
728                 fileToWrite << line << std::endl;
729                 continue;
730             }
731             // skip comments after meaningful section and process those
732             line = line.substr(0, startPos);
733         }
734         if (line.find(moduleName) != std::string::npos)
735         {
736             if ((startPos = line.find(argSearch)) != std::string::npos)
737             {
738                 if ((endPos = line.find(' ', startPos)) == std::string::npos)
739                 {
740                     endPos = line.size();
741                 }
742                 startPos += argSearch.size();
743                 fileToWrite << line.substr(0, startPos) << argValue
744                             << line.substr(endPos, line.size() - endPos)
745                             << std::endl;
746                 found = true;
747                 continue;
748             }
749         }
750         fileToWrite << line << std::endl;
751     }
752     fileToWrite.close();
753     fileToRead.close();
754     if (found)
755     {
756         if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
757         {
758             return success;
759         }
760     }
761     return failure;
762 }
763 
764 void UserMgr::userEnable(const std::string& userName, bool enabled)
765 {
766     // All user management lock has to be based on /etc/shadow
767     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
768     throwForUserDoesNotExist(userName);
769     try
770     {
771         executeUserModifyUserEnable(userName.c_str(), enabled);
772     }
773     catch (const InternalFailure& e)
774     {
775         log<level::ERR>("Unable to modify user enabled state");
776         elog<InternalFailure>();
777     }
778 
779     log<level::INFO>("User enabled/disabled state updated successfully",
780                      entry("USER_NAME=%s", userName.c_str()),
781                      entry("ENABLED=%d", enabled));
782     usersList[userName]->setUserEnabled(enabled);
783     return;
784 }
785 
786 /**
787  * pam_tally2 app will provide the user failure count and failure status
788  * in second line of output with words position [0] - user name,
789  * [1] - failure count, [2] - latest failure date, [3] - latest failure time
790  * [4] - failure app
791  **/
792 
793 static constexpr size_t t2FailCntIdx = 1;
794 static constexpr size_t t2FailDateIdx = 2;
795 static constexpr size_t t2FailTimeIdx = 3;
796 static constexpr size_t t2OutputIndex = 1;
797 
798 bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
799 {
800     // All user management lock has to be based on /etc/shadow
801     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
802     if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
803     {
804         return false;
805     }
806 
807     std::vector<std::string> output;
808     try
809     {
810         output = getFailedAttempt(userName.c_str());
811     }
812     catch (const InternalFailure& e)
813     {
814         log<level::ERR>("Unable to read login failure counter");
815         elog<InternalFailure>();
816     }
817 
818     std::vector<std::string> splitWords;
819     boost::algorithm::split(splitWords, output[t2OutputIndex],
820                             boost::algorithm::is_any_of("\t "),
821                             boost::token_compress_on);
822     uint16_t failAttempts = 0;
823     try
824     {
825         unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
826         if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
827         {
828             throw std::out_of_range("Out of range");
829         }
830         failAttempts = static_cast<decltype(failAttempts)>(tmp);
831     }
832     catch (const std::exception& e)
833     {
834         log<level::ERR>("Exception for userLockedForFailedAttempt",
835                         entry("WHAT=%s", e.what()));
836         elog<InternalFailure>();
837     }
838 
839     if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
840     {
841         return false;
842     }
843 
844     // When failedAttempts is not 0, Latest failure date/time should be
845     // available
846     if (splitWords.size() < 4)
847     {
848         log<level::ERR>("Unable to read latest failure date/time");
849         elog<InternalFailure>();
850     }
851 
852     const std::string failDateTime =
853         splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
854 
855     // NOTE: Cannot use std::get_time() here as the implementation of %y in
856     // libstdc++ does not match POSIX strptime() before gcc 12.1.0
857     // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
858     std::tm tmStruct = {};
859     if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
860     {
861         log<level::ERR>("Failed to parse latest failure date/time");
862         elog<InternalFailure>();
863     }
864 
865     time_t failTimestamp = std::mktime(&tmStruct);
866     if (failTimestamp +
867             static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
868         std::time(NULL))
869     {
870         return false;
871     }
872 
873     return true;
874 }
875 
876 bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
877                                          const bool& value)
878 {
879     // All user management lock has to be based on /etc/shadow
880     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
881     if (value == true)
882     {
883         return userLockedForFailedAttempt(userName);
884     }
885 
886     try
887     {
888         executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
889     }
890     catch (const InternalFailure& e)
891     {
892         log<level::ERR>("Unable to reset login failure counter");
893         elog<InternalFailure>();
894     }
895 
896     return userLockedForFailedAttempt(userName);
897 }
898 
899 bool UserMgr::userPasswordExpired(const std::string& userName)
900 {
901     // All user management lock has to be based on /etc/shadow
902     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
903 
904     struct spwd spwd
905     {};
906     struct spwd* spwdPtr = nullptr;
907     auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
908     if (buflen < -1)
909     {
910         // Use a default size if there is no hard limit suggested by sysconf()
911         buflen = 1024;
912     }
913     std::vector<char> buffer(buflen);
914     auto status =
915         getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
916     // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
917     // If no matching password record was found, these functions return 0
918     // and store NULL in *spwdPtr
919     if ((status == 0) && (&spwd == spwdPtr))
920     {
921         // Determine password validity per "chage" docs, where:
922         //   spwd.sp_lstchg == 0 means password is expired, and
923         //   spwd.sp_max == -1 means the password does not expire.
924         constexpr long secondsPerDay = 60 * 60 * 24;
925         long today = static_cast<long>(time(NULL)) / secondsPerDay;
926         if ((spwd.sp_lstchg == 0) ||
927             ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
928         {
929             return true;
930         }
931     }
932     else
933     {
934         // User entry is missing in /etc/shadow, indicating no SHA password.
935         // Treat this as new user without password entry in /etc/shadow
936         // TODO: Add property to indicate user password was not set yet
937         // https://github.com/openbmc/phosphor-user-manager/issues/8
938         return false;
939     }
940 
941     return false;
942 }
943 
944 UserSSHLists UserMgr::getUserAndSshGrpList()
945 {
946     // All user management lock has to be based on /etc/shadow
947     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
948 
949     std::vector<std::string> userList;
950     std::vector<std::string> sshUsersList;
951     struct passwd pw, *pwp = nullptr;
952     std::array<char, 1024> buffer{};
953 
954     phosphor::user::File passwd(passwdFileName, "r");
955     if ((passwd)() == NULL)
956     {
957         log<level::ERR>("Error opening the passwd file");
958         elog<InternalFailure>();
959     }
960 
961     while (true)
962     {
963         auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
964                              &pwp);
965         if ((r != 0) || (pwp == NULL))
966         {
967             // Any error, break the loop.
968             break;
969         }
970 #ifdef ENABLE_ROOT_USER_MGMT
971         // Add all users whose UID >= 1000 and < 65534
972         // and special UID 0.
973         if ((pwp->pw_uid == 0) ||
974             ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
975 #else
976         // Add all users whose UID >=1000 and < 65534
977         if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
978 #endif
979         {
980             std::string userName(pwp->pw_name);
981             userList.emplace_back(userName);
982 
983             // ssh doesn't have separate group. Check login shell entry to
984             // get all users list which are member of ssh group.
985             std::string loginShell(pwp->pw_shell);
986             if (loginShell == "/bin/sh")
987             {
988                 sshUsersList.emplace_back(userName);
989             }
990         }
991     }
992     endpwent();
993     return std::make_pair(std::move(userList), std::move(sshUsersList));
994 }
995 
996 size_t UserMgr::getIpmiUsersCount()
997 {
998     std::vector<std::string> userList = getUsersInGroup("ipmi");
999     return userList.size();
1000 }
1001 
1002 size_t UserMgr::getNonIpmiUsersCount()
1003 {
1004     std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
1005     return usersList.size() - ipmiUsers.size();
1006 }
1007 
1008 bool UserMgr::isUserEnabled(const std::string& userName)
1009 {
1010     // All user management lock has to be based on /etc/shadow
1011     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1012     std::array<char, 4096> buffer{};
1013     struct spwd spwd;
1014     struct spwd* resultPtr = nullptr;
1015     int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
1016                             buffer.max_size(), &resultPtr);
1017     if (!status && (&spwd == resultPtr))
1018     {
1019         if (resultPtr->sp_expire >= 0)
1020         {
1021             return false; // user locked out
1022         }
1023         return true;
1024     }
1025     return false; // assume user is disabled for any error.
1026 }
1027 
1028 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
1029 {
1030     std::vector<std::string> usersInGroup;
1031     // Should be more than enough to get the pwd structure.
1032     std::array<char, 4096> buffer{};
1033     struct group grp;
1034     struct group* resultPtr = nullptr;
1035 
1036     int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1037                             buffer.max_size(), &resultPtr);
1038 
1039     if (!status && (&grp == resultPtr))
1040     {
1041         for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
1042         {
1043             usersInGroup.emplace_back(*(grp.gr_mem));
1044         }
1045     }
1046     else
1047     {
1048         log<level::ERR>("Group not found",
1049                         entry("GROUP=%s", groupName.c_str()));
1050         // Don't throw error, just return empty userList - fallback
1051     }
1052     return usersInGroup;
1053 }
1054 
1055 DbusUserObj UserMgr::getPrivilegeMapperObject(void)
1056 {
1057     DbusUserObj objects;
1058     try
1059     {
1060         std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
1061         std::string interface = "xyz.openbmc_project.User.Ldap.Config";
1062 
1063         auto ldapMgmtService =
1064             getServiceName(std::move(basePath), std::move(interface));
1065         auto method = bus.new_method_call(
1066             ldapMgmtService.c_str(), ldapMgrObjBasePath,
1067             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1068 
1069         auto reply = bus.call(method);
1070         reply.read(objects);
1071     }
1072     catch (const InternalFailure& e)
1073     {
1074         log<level::ERR>("Unable to get the User Service",
1075                         entry("WHAT=%s", e.what()));
1076         throw;
1077     }
1078     catch (const sdbusplus::exception_t& e)
1079     {
1080         log<level::ERR>(
1081             "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
1082             entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
1083         throw;
1084     }
1085     return objects;
1086 }
1087 
1088 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
1089 {
1090     auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1091                                           objMapperInterface, "GetObject");
1092 
1093     mapperCall.append(std::move(path));
1094     mapperCall.append(std::vector<std::string>({std::move(intf)}));
1095 
1096     auto mapperResponseMsg = bus.call(mapperCall);
1097 
1098     if (mapperResponseMsg.is_method_error())
1099     {
1100         log<level::ERR>("Error in mapper call");
1101         elog<InternalFailure>();
1102     }
1103 
1104     std::map<std::string, std::vector<std::string>> mapperResponse;
1105     mapperResponseMsg.read(mapperResponse);
1106 
1107     if (mapperResponse.begin() == mapperResponse.end())
1108     {
1109         log<level::ERR>("Invalid response from mapper");
1110         elog<InternalFailure>();
1111     }
1112 
1113     return mapperResponse.begin()->first;
1114 }
1115 
1116 gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
1117 {
1118     static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1119     if (buflen <= 0)
1120     {
1121         // Use a default size if there is no hard limit suggested by sysconf()
1122         buflen = 1024;
1123     }
1124 
1125     struct passwd pwd;
1126     struct passwd* pwdPtr = nullptr;
1127     std::vector<char> buffer(buflen);
1128 
1129     auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(),
1130                              buffer.size(), &pwdPtr);
1131     // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
1132     // If no matching password record was found, these functions return 0
1133     // and store NULL in *pwdPtr
1134     if (!status && (&pwd == pwdPtr))
1135     {
1136         return pwd.pw_gid;
1137     }
1138 
1139     log<level::ERR>("User noes not exist",
1140                     entry("USER_NAME=%s", userName.c_str()));
1141     elog<UserNameDoesNotExist>();
1142 }
1143 
1144 bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
1145                             const std::string& groupName) const
1146 {
1147     static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1148     if (buflen <= 0)
1149     {
1150         // Use a default size if there is no hard limit suggested by sysconf()
1151         buflen = 1024;
1152     }
1153 
1154     struct group grp;
1155     struct group* grpPtr = nullptr;
1156     std::vector<char> buffer(buflen);
1157 
1158     auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1159                              buffer.size(), &grpPtr);
1160 
1161     // Groups with a lot of members may require a buffer of bigger size than
1162     // suggested by _SC_GETGR_R_SIZE_MAX.
1163     // 32K should be enough for about 2K members.
1164     constexpr auto maxBufferLength = 32 * 1024;
1165     while (status == ERANGE && buflen < maxBufferLength)
1166     {
1167         buflen *= 2;
1168         buffer.resize(buflen);
1169 
1170         log<level::DEBUG>("Increase buffer for getgrnam_r()",
1171                           entry("BUFFER_LENGTH=%zu", buflen));
1172 
1173         status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1174                             buffer.size(), &grpPtr);
1175     }
1176 
1177     // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
1178     // If no matching group record was found, these functions return 0
1179     // and store NULL in *grpPtr
1180     if (!status && (&grp == grpPtr))
1181     {
1182         if (primaryGid == grp.gr_gid)
1183         {
1184             return true;
1185         }
1186 
1187         for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
1188         {
1189             if (userName == grp.gr_mem[i])
1190             {
1191                 return true;
1192             }
1193         }
1194     }
1195     else if (status == ERANGE)
1196     {
1197         log<level::ERR>("Group info requires too much memory",
1198                         entry("GROUP_NAME=%s", groupName.c_str()));
1199     }
1200     else
1201     {
1202         log<level::ERR>("Group does not exist",
1203                         entry("GROUP_NAME=%s", groupName.c_str()));
1204     }
1205 
1206     return false;
1207 }
1208 
1209 void UserMgr::executeGroupCreation(const char* groupName)
1210 {
1211     executeCmd("/usr/sbin/groupadd", groupName);
1212 }
1213 
1214 void UserMgr::executeGroupDeletion(const char* groupName)
1215 {
1216     executeCmd("/usr/sbin/groupdel", groupName);
1217 }
1218 
1219 UserInfoMap UserMgr::getUserInfo(std::string userName)
1220 {
1221     UserInfoMap userInfo;
1222     // Check whether the given user is local user or not.
1223     if (isUserExist(userName))
1224     {
1225         const auto& user = usersList[userName];
1226         userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1227         userInfo.emplace("UserGroups", user.get()->userGroups());
1228         userInfo.emplace("UserEnabled", user.get()->userEnabled());
1229         userInfo.emplace("UserLockedForFailedAttempt",
1230                          user.get()->userLockedForFailedAttempt());
1231         userInfo.emplace("UserPasswordExpired",
1232                          user.get()->userPasswordExpired());
1233         userInfo.emplace("RemoteUser", false);
1234     }
1235     else
1236     {
1237         auto primaryGid = getPrimaryGroup(userName);
1238 
1239         DbusUserObj objects = getPrivilegeMapperObject();
1240 
1241         std::string ldapConfigPath;
1242         std::string userPrivilege;
1243 
1244         try
1245         {
1246             for (const auto& [path, interfaces] : objects)
1247             {
1248                 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
1249                 if (it != interfaces.end())
1250                 {
1251                     auto propIt = it->second.find("Enabled");
1252                     if (propIt != it->second.end() &&
1253                         std::get<bool>(propIt->second))
1254                     {
1255                         ldapConfigPath = path.str + '/';
1256                         break;
1257                     }
1258                 }
1259             }
1260 
1261             if (ldapConfigPath.empty())
1262             {
1263                 return userInfo;
1264             }
1265 
1266             for (const auto& [path, interfaces] : objects)
1267             {
1268                 if (!path.str.starts_with(ldapConfigPath))
1269                 {
1270                     continue;
1271                 }
1272 
1273                 auto it = interfaces.find(
1274                     "xyz.openbmc_project.User.PrivilegeMapperEntry");
1275                 if (it != interfaces.end())
1276                 {
1277                     std::string privilege;
1278                     std::string groupName;
1279 
1280                     for (const auto& [propName, propValue] : it->second)
1281                     {
1282                         if (propName == "GroupName")
1283                         {
1284                             groupName = std::get<std::string>(propValue);
1285                         }
1286                         else if (propName == "Privilege")
1287                         {
1288                             privilege = std::get<std::string>(propValue);
1289                         }
1290                     }
1291 
1292                     if (!groupName.empty() && !privilege.empty() &&
1293                         isGroupMember(userName, primaryGid, groupName))
1294                     {
1295                         userPrivilege = privilege;
1296                         break;
1297                     }
1298                 }
1299                 if (!userPrivilege.empty())
1300                 {
1301                     break;
1302                 }
1303             }
1304 
1305             if (!userPrivilege.empty())
1306             {
1307                 userInfo.emplace("UserPrivilege", userPrivilege);
1308             }
1309             else
1310             {
1311                 log<level::WARNING>("LDAP group privilege mapping does not "
1312                                     "exist, default \"priv-user\" is used");
1313                 userInfo.emplace("UserPrivilege", "priv-user");
1314             }
1315         }
1316         catch (const std::bad_variant_access& e)
1317         {
1318             log<level::ERR>("Error while accessing variant",
1319                             entry("WHAT=%s", e.what()));
1320             elog<InternalFailure>();
1321         }
1322         userInfo.emplace("RemoteUser", true);
1323     }
1324 
1325     return userInfo;
1326 }
1327 
1328 void UserMgr::initializeAccountPolicy()
1329 {
1330     std::string valueStr;
1331     auto value = minPasswdLength;
1332     unsigned long tmp = 0;
1333     if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1334         success)
1335     {
1336         AccountPolicyIface::minPasswordLength(minPasswdLength);
1337     }
1338     else
1339     {
1340         try
1341         {
1342             tmp = std::stoul(valueStr, nullptr);
1343             if (tmp > std::numeric_limits<decltype(value)>::max())
1344             {
1345                 throw std::out_of_range("Out of range");
1346             }
1347             value = static_cast<decltype(value)>(tmp);
1348         }
1349         catch (const std::exception& e)
1350         {
1351             log<level::ERR>("Exception for MinPasswordLength",
1352                             entry("WHAT=%s", e.what()));
1353             throw;
1354         }
1355         AccountPolicyIface::minPasswordLength(value);
1356     }
1357     valueStr.clear();
1358     if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1359         success)
1360     {
1361         AccountPolicyIface::rememberOldPasswordTimes(0);
1362     }
1363     else
1364     {
1365         value = 0;
1366         try
1367         {
1368             tmp = std::stoul(valueStr, nullptr);
1369             if (tmp > std::numeric_limits<decltype(value)>::max())
1370             {
1371                 throw std::out_of_range("Out of range");
1372             }
1373             value = static_cast<decltype(value)>(tmp);
1374         }
1375         catch (const std::exception& e)
1376         {
1377             log<level::ERR>("Exception for RememberOldPasswordTimes",
1378                             entry("WHAT=%s", e.what()));
1379             throw;
1380         }
1381         AccountPolicyIface::rememberOldPasswordTimes(value);
1382     }
1383     valueStr.clear();
1384     if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1385     {
1386         AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1387     }
1388     else
1389     {
1390         uint16_t value16 = 0;
1391         try
1392         {
1393             tmp = std::stoul(valueStr, nullptr);
1394             if (tmp > std::numeric_limits<decltype(value16)>::max())
1395             {
1396                 throw std::out_of_range("Out of range");
1397             }
1398             value16 = static_cast<decltype(value16)>(tmp);
1399         }
1400         catch (const std::exception& e)
1401         {
1402             log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1403                             entry("WHAT=%s", e.what()));
1404             throw;
1405         }
1406         AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1407     }
1408     valueStr.clear();
1409     if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1410     {
1411         AccountPolicyIface::accountUnlockTimeout(0);
1412     }
1413     else
1414     {
1415         uint32_t value32 = 0;
1416         try
1417         {
1418             tmp = std::stoul(valueStr, nullptr);
1419             if (tmp > std::numeric_limits<decltype(value32)>::max())
1420             {
1421                 throw std::out_of_range("Out of range");
1422             }
1423             value32 = static_cast<decltype(value32)>(tmp);
1424         }
1425         catch (const std::exception& e)
1426         {
1427             log<level::ERR>("Exception for AccountUnlockTimeout",
1428                             entry("WHAT=%s", e.what()));
1429             throw;
1430         }
1431         AccountPolicyIface::accountUnlockTimeout(value32);
1432     }
1433 }
1434 
1435 void UserMgr::initUserObjects(void)
1436 {
1437     // All user management lock has to be based on /etc/shadow
1438     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1439     std::vector<std::string> userNameList;
1440     std::vector<std::string> sshGrpUsersList;
1441     UserSSHLists userSSHLists = getUserAndSshGrpList();
1442     userNameList = std::move(userSSHLists.first);
1443     sshGrpUsersList = std::move(userSSHLists.second);
1444 
1445     if (!userNameList.empty())
1446     {
1447         std::map<std::string, std::vector<std::string>> groupLists;
1448         // We only track users that are in the |predefinedGroups|
1449         // The other groups don't contain real BMC users.
1450         for (const char* grp : predefinedGroups)
1451         {
1452             if (grp == grpSsh)
1453             {
1454                 groupLists.emplace(grp, sshGrpUsersList);
1455             }
1456             else
1457             {
1458                 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1459                 groupLists.emplace(grp, grpUsersList);
1460             }
1461         }
1462         for (auto& grp : privMgr)
1463         {
1464             std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1465             groupLists.emplace(grp, grpUsersList);
1466         }
1467 
1468         for (auto& user : userNameList)
1469         {
1470             std::vector<std::string> userGroups;
1471             std::string userPriv;
1472             for (const auto& grp : groupLists)
1473             {
1474                 std::vector<std::string> tempGrp = grp.second;
1475                 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1476                     tempGrp.end())
1477                 {
1478                     if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1479                         privMgr.end())
1480                     {
1481                         userPriv = grp.first;
1482                     }
1483                     else
1484                     {
1485                         userGroups.emplace_back(grp.first);
1486                     }
1487                 }
1488             }
1489             // Add user objects to the Users path.
1490             sdbusplus::message::object_path tempObjPath(usersObjPath);
1491             tempObjPath /= user;
1492             std::string objPath(tempObjPath);
1493             std::sort(userGroups.begin(), userGroups.end());
1494             usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1495                                         bus, objPath.c_str(), userGroups,
1496                                         userPriv, isUserEnabled(user), *this));
1497         }
1498     }
1499 }
1500 
1501 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1502     Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1503     pamPasswdConfigFile(defaultPamPasswdConfigFile),
1504     pamAuthConfigFile(defaultPamAuthConfigFile)
1505 {
1506     UserMgrIface::allPrivileges(privMgr);
1507     groupsMgr = readAllGroupsOnSystem();
1508     std::sort(groupsMgr.begin(), groupsMgr.end());
1509     UserMgrIface::allGroups(groupsMgr);
1510     initializeAccountPolicy();
1511     initUserObjects();
1512 
1513     // emit the signal
1514     this->emit_object_added();
1515 }
1516 
1517 void UserMgr::executeUserAdd(const char* userName, const char* groups,
1518                              bool sshRequested, bool enabled)
1519 {
1520     // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1521     // 1970-01-01, that's an implementation-defined behavior
1522     executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
1523                (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
1524                (enabled ? "" : "1970-01-01"));
1525 }
1526 
1527 void UserMgr::executeUserDelete(const char* userName)
1528 {
1529     executeCmd("/usr/sbin/userdel", userName, "-r");
1530 }
1531 
1532 void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1533 {
1534     std::string newHomeDir = "/home/";
1535     newHomeDir += newUserName;
1536     executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1537                newHomeDir.c_str(), "-m");
1538 }
1539 
1540 void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1541                                 bool sshRequested)
1542 {
1543     executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
1544                (sshRequested ? "/bin/sh" : "/sbin/nologin"));
1545 }
1546 
1547 void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1548 {
1549     // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1550     // 1970-01-01, that's an implementation-defined behavior
1551     executeCmd("/usr/sbin/usermod", userName, "-e",
1552                (enabled ? "" : "1970-01-01"));
1553 }
1554 
1555 std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1556 {
1557     return executeCmd("/usr/sbin/pam_tally2", "-u", userName);
1558 }
1559 
1560 } // namespace user
1561 } // namespace phosphor
1562