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 <shadow.h>
18 #include <unistd.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21 #include <fstream>
22 #include <grp.h>
23 #include <pwd.h>
24 #include <regex>
25 #include <algorithm>
26 #include <numeric>
27 #include <boost/process/child.hpp>
28 #include <boost/process/io.hpp>
29 #include <boost/algorithm/string/split.hpp>
30 #include <xyz/openbmc_project/Common/error.hpp>
31 #include <xyz/openbmc_project/User/Common/error.hpp>
32 #include <phosphor-logging/log.hpp>
33 #include <phosphor-logging/elog.hpp>
34 #include <phosphor-logging/elog-errors.hpp>
35 #include "shadowlock.hpp"
36 #include "file.hpp"
37 #include "user_mgr.hpp"
38 #include "users.hpp"
39 #include "config.h"
40 
41 namespace phosphor
42 {
43 namespace user
44 {
45 
46 static constexpr const char *passwdFileName = "/etc/passwd";
47 static constexpr size_t ipmiMaxUsers = 15;
48 static constexpr size_t ipmiMaxUserNameLen = 16;
49 static constexpr size_t systemMaxUserNameLen = 30;
50 static constexpr size_t maxSystemUsers = 30;
51 static constexpr const char *grpSsh = "ssh";
52 static constexpr uint8_t minPasswdLength = 8;
53 static constexpr int success = 0;
54 static constexpr int failure = -1;
55 
56 // pam modules related
57 static constexpr const char *pamTally2 = "pam_tally2.so";
58 static constexpr const char *pamCrackLib = "pam_cracklib.so";
59 static constexpr const char *pamPWHistory = "pam_pwhistory.so";
60 static constexpr const char *minPasswdLenProp = "minlen";
61 static constexpr const char *remOldPasswdCount = "remember";
62 static constexpr const char *maxFailedAttempt = "deny";
63 static constexpr const char *unlockTimeout = "unlock_time";
64 static constexpr const char *pamPasswdConfigFile = "/etc/pam.d/common-password";
65 static constexpr const char *pamAuthConfigFile = "/etc/pam.d/common-auth";
66 
67 using namespace phosphor::logging;
68 using InsufficientPermission =
69     sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
70 using InternalFailure =
71     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
72 using InvalidArgument =
73     sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
74 using UserNameExists =
75     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
76 using UserNameDoesNotExist =
77     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
78 using UserNameGroupFail =
79     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
80 
81 using NoResource =
82     sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
83 
84 using Argument = xyz::openbmc_project::Common::InvalidArgument;
85 
86 template <typename... ArgTypes>
87 static std::vector<std::string> executeCmd(const char *path,
88                                            ArgTypes &&... tArgs)
89 {
90     std::vector<std::string> stdOutput;
91     boost::process::ipstream stdOutStream;
92     boost::process::child execProg(path, const_cast<char *>(tArgs)...,
93                                    boost::process::std_out > stdOutStream);
94     std::string stdOutLine;
95 
96     while (stdOutStream && std::getline(stdOutStream, stdOutLine) &&
97            !stdOutLine.empty())
98     {
99         stdOutput.emplace_back(stdOutLine);
100     }
101 
102     execProg.wait();
103 
104     int retCode = execProg.exit_code();
105     if (retCode)
106     {
107         log<level::ERR>("Command execution failed", entry("PATH=%d", path),
108                         entry("RETURN_CODE:%d", retCode));
109         elog<InternalFailure>();
110     }
111 
112     return stdOutput;
113 }
114 
115 static std::string getCSVFromVector(std::vector<std::string> vec)
116 {
117     switch (vec.size())
118     {
119         case 0:
120         {
121             return "";
122         }
123         break;
124 
125         case 1:
126         {
127             return std::string{vec[0]};
128         }
129         break;
130 
131         default:
132         {
133             return std::accumulate(
134                 std::next(vec.begin()), vec.end(), vec[0],
135                 [](std::string a, std::string b) { return a + ',' + b; });
136         }
137     }
138 }
139 
140 static bool removeStringFromCSV(std::string &csvStr, const std::string &delStr)
141 {
142     std::string::size_type delStrPos = csvStr.find(delStr);
143     if (delStrPos != std::string::npos)
144     {
145         // need to also delete the comma char
146         if (delStrPos == 0)
147         {
148             csvStr.erase(delStrPos, delStr.size() + 1);
149         }
150         else
151         {
152             csvStr.erase(delStrPos - 1, delStr.size() + 1);
153         }
154         return true;
155     }
156     return false;
157 }
158 
159 bool UserMgr::isUserExist(const std::string &userName)
160 {
161     if (userName.empty())
162     {
163         log<level::ERR>("User name is empty");
164         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
165                               Argument::ARGUMENT_VALUE("Null"));
166     }
167     if (usersList.find(userName) == usersList.end())
168     {
169         return false;
170     }
171     return true;
172 }
173 
174 void UserMgr::throwForUserDoesNotExist(const std::string &userName)
175 {
176     if (isUserExist(userName) == false)
177     {
178         log<level::ERR>("User does not exist",
179                         entry("USER_NAME=%s", userName.c_str()));
180         elog<UserNameDoesNotExist>();
181     }
182 }
183 
184 void UserMgr::throwForUserExists(const std::string &userName)
185 {
186     if (isUserExist(userName) == true)
187     {
188         log<level::ERR>("User already exists",
189                         entry("USER_NAME=%s", userName.c_str()));
190         elog<UserNameExists>();
191     }
192 }
193 
194 void UserMgr::throwForUserNameConstraints(
195     const std::string &userName, const std::vector<std::string> &groupNames)
196 {
197     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
198         groupNames.end())
199     {
200         if (userName.length() > ipmiMaxUserNameLen)
201         {
202             log<level::ERR>("IPMI user name length limitation",
203                             entry("SIZE=%d", userName.length()));
204             elog<UserNameGroupFail>(
205                 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
206                     "IPMI length"));
207         }
208     }
209     if (userName.length() > systemMaxUserNameLen)
210     {
211         log<level::ERR>("User name length limitation",
212                         entry("SIZE=%d", userName.length()));
213         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
214                               Argument::ARGUMENT_VALUE("Invalid length"));
215     }
216     if (!std::regex_match(userName.c_str(),
217                           std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
218     {
219         log<level::ERR>("Invalid user name",
220                         entry("USER_NAME=%s", userName.c_str()));
221         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
222                               Argument::ARGUMENT_VALUE("Invalid data"));
223     }
224 }
225 
226 void UserMgr::throwForMaxGrpUserCount(
227     const std::vector<std::string> &groupNames)
228 {
229     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
230         groupNames.end())
231     {
232         if (getIpmiUsersCount() >= ipmiMaxUsers)
233         {
234             log<level::ERR>("IPMI user limit reached");
235             elog<NoResource>(
236                 xyz::openbmc_project::User::Common::NoResource::REASON(
237                     "ipmi user count reached"));
238         }
239     }
240     else
241     {
242         if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
243                                         (maxSystemUsers - ipmiMaxUsers))
244         {
245             log<level::ERR>("Non-ipmi User limit reached");
246             elog<NoResource>(
247                 xyz::openbmc_project::User::Common::NoResource::REASON(
248                     "Non-ipmi user count reached"));
249         }
250     }
251     return;
252 }
253 
254 void UserMgr::throwForInvalidPrivilege(const std::string &priv)
255 {
256     if (!priv.empty() &&
257         (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
258     {
259         log<level::ERR>("Invalid privilege");
260         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
261                               Argument::ARGUMENT_VALUE(priv.c_str()));
262     }
263 }
264 
265 void UserMgr::throwForInvalidGroups(const std::vector<std::string> &groupNames)
266 {
267     for (auto &group : groupNames)
268     {
269         if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
270             groupsMgr.end())
271         {
272             log<level::ERR>("Invalid Group Name listed");
273             elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
274                                   Argument::ARGUMENT_VALUE(group.c_str()));
275         }
276     }
277 }
278 
279 void UserMgr::createUser(std::string userName,
280                          std::vector<std::string> groupNames, std::string priv,
281                          bool enabled)
282 {
283     throwForInvalidPrivilege(priv);
284     throwForInvalidGroups(groupNames);
285     // All user management lock has to be based on /etc/shadow
286     phosphor::user::shadow::Lock lock();
287     throwForUserExists(userName);
288     throwForUserNameConstraints(userName, groupNames);
289     throwForMaxGrpUserCount(groupNames);
290 
291     std::string groups = getCSVFromVector(groupNames);
292     bool sshRequested = removeStringFromCSV(groups, grpSsh);
293 
294     // treat privilege as a group - This is to avoid using different file to
295     // store the same.
296     if (!priv.empty())
297     {
298         if (groups.size() != 0)
299         {
300             groups += ",";
301         }
302         groups += priv;
303     }
304     try
305     {
306         executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
307                    "-m", "-N", "-s",
308                    (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
309                    (enabled ? "" : "1970-01-02"));
310     }
311     catch (const InternalFailure &e)
312     {
313         log<level::ERR>("Unable to create new user");
314         elog<InternalFailure>();
315     }
316 
317     // Add the users object before sending out the signal
318     std::string userObj = std::string(usersObjPath) + "/" + userName;
319     std::sort(groupNames.begin(), groupNames.end());
320     usersList.emplace(
321         userName, std::move(std::make_unique<phosphor::user::Users>(
322                       bus, userObj.c_str(), groupNames, priv, enabled, *this)));
323 
324     log<level::INFO>("User created successfully",
325                      entry("USER_NAME=%s", userName.c_str()));
326     return;
327 }
328 
329 void UserMgr::deleteUser(std::string userName)
330 {
331     // All user management lock has to be based on /etc/shadow
332     phosphor::user::shadow::Lock lock();
333     throwForUserDoesNotExist(userName);
334     try
335     {
336         executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
337     }
338     catch (const InternalFailure &e)
339     {
340         log<level::ERR>("User delete failed",
341                         entry("USER_NAME=%s", userName.c_str()));
342         elog<InternalFailure>();
343     }
344 
345     usersList.erase(userName);
346 
347     log<level::INFO>("User deleted successfully",
348                      entry("USER_NAME=%s", userName.c_str()));
349     return;
350 }
351 
352 void UserMgr::renameUser(std::string userName, std::string newUserName)
353 {
354     // All user management lock has to be based on /etc/shadow
355     phosphor::user::shadow::Lock lock();
356     throwForUserDoesNotExist(userName);
357     throwForUserExists(newUserName);
358     throwForUserNameConstraints(newUserName,
359                                 usersList[userName].get()->userGroups());
360     try
361     {
362         std::string newHomeDir = "/home/" + newUserName;
363         executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
364                    userName.c_str(), "-d", newHomeDir.c_str(), "-m");
365     }
366     catch (const InternalFailure &e)
367     {
368         log<level::ERR>("User rename failed",
369                         entry("USER_NAME=%s", userName.c_str()));
370         elog<InternalFailure>();
371     }
372     const auto &user = usersList[userName];
373     std::string priv = user.get()->userPrivilege();
374     std::vector<std::string> groupNames = user.get()->userGroups();
375     bool enabled = user.get()->userEnabled();
376     std::string newUserObj = std::string(usersObjPath) + "/" + newUserName;
377     // Special group 'ipmi' needs a way to identify user renamed, in order to
378     // update encrypted password. It can't rely only on InterfacesRemoved &
379     // InterfacesAdded. So first send out userRenamed signal.
380     this->userRenamed(userName, newUserName);
381     usersList.erase(userName);
382     usersList.emplace(
383         newUserName,
384         std::move(std::make_unique<phosphor::user::Users>(
385             bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
386     return;
387 }
388 
389 void UserMgr::updateGroupsAndPriv(const std::string &userName,
390                                   const std::vector<std::string> &groupNames,
391                                   const std::string &priv)
392 {
393     throwForInvalidPrivilege(priv);
394     throwForInvalidGroups(groupNames);
395     // All user management lock has to be based on /etc/shadow
396     phosphor::user::shadow::Lock lock();
397     throwForUserDoesNotExist(userName);
398     const std::vector<std::string> &oldGroupNames =
399         usersList[userName].get()->userGroups();
400     std::vector<std::string> groupDiff;
401     // Note: already dealing with sorted group lists.
402     std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
403                                   groupNames.begin(), groupNames.end(),
404                                   std::back_inserter(groupDiff));
405     if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
406         groupDiff.end())
407     {
408         throwForUserNameConstraints(userName, groupNames);
409         throwForMaxGrpUserCount(groupNames);
410     }
411 
412     std::string groups = getCSVFromVector(groupNames);
413     bool sshRequested = removeStringFromCSV(groups, grpSsh);
414 
415     // treat privilege as a group - This is to avoid using different file to
416     // store the same.
417     if (!priv.empty())
418     {
419         if (groups.size() != 0)
420         {
421             groups += ",";
422         }
423         groups += priv;
424     }
425     try
426     {
427         executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
428                    "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
429     }
430     catch (const InternalFailure &e)
431     {
432         log<level::ERR>("Unable to modify user privilege / groups");
433         elog<InternalFailure>();
434     }
435 
436     log<level::INFO>("User groups / privilege updated successfully",
437                      entry("USER_NAME=%s", userName.c_str()));
438     return;
439 }
440 
441 uint8_t UserMgr::minPasswordLength(uint8_t value)
442 {
443     if (value == AccountPolicyIface::minPasswordLength())
444     {
445         return value;
446     }
447     if (value < minPasswdLength)
448     {
449         return value;
450     }
451     if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
452                              std::to_string(value)) != success)
453     {
454         log<level::ERR>("Unable to set minPasswordLength");
455         elog<InternalFailure>();
456     }
457     return AccountPolicyIface::minPasswordLength(value);
458 }
459 
460 uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
461 {
462     if (value == AccountPolicyIface::rememberOldPasswordTimes())
463     {
464         return value;
465     }
466     if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
467                              std::to_string(value)) != success)
468     {
469         log<level::ERR>("Unable to set rememberOldPasswordTimes");
470         elog<InternalFailure>();
471     }
472     return AccountPolicyIface::rememberOldPasswordTimes(value);
473 }
474 
475 uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
476 {
477     if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
478     {
479         return value;
480     }
481     if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
482                              std::to_string(value)) != success)
483     {
484         log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
485         elog<InternalFailure>();
486     }
487     return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
488 }
489 
490 uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
491 {
492     if (value == AccountPolicyIface::accountUnlockTimeout())
493     {
494         return value;
495     }
496     if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
497         success)
498     {
499         log<level::ERR>("Unable to set accountUnlockTimeout");
500         elog<InternalFailure>();
501     }
502     return AccountPolicyIface::accountUnlockTimeout(value);
503 }
504 
505 int UserMgr::getPamModuleArgValue(const std::string &moduleName,
506                                   const std::string &argName,
507                                   std::string &argValue)
508 {
509     std::string fileName;
510     if (moduleName == pamTally2)
511     {
512         fileName = pamAuthConfigFile;
513     }
514     else
515     {
516         fileName = pamPasswdConfigFile;
517     }
518     std::ifstream fileToRead(fileName, std::ios::in);
519     if (!fileToRead.is_open())
520     {
521         log<level::ERR>("Failed to open pam configuration file",
522                         entry("FILE_NAME=%s", fileName.c_str()));
523         return failure;
524     }
525     std::string line;
526     auto argSearch = argName + "=";
527     size_t startPos = 0;
528     size_t endPos = 0;
529     while (getline(fileToRead, line))
530     {
531         // skip comments section starting with #
532         if ((startPos = line.find('#')) != std::string::npos)
533         {
534             if (startPos == 0)
535             {
536                 continue;
537             }
538             // skip comments after meaningful section and process those
539             line = line.substr(0, startPos);
540         }
541         if (line.find(moduleName) != std::string::npos)
542         {
543             if ((startPos = line.find(argSearch)) != std::string::npos)
544             {
545                 if ((endPos = line.find(' ', startPos)) == std::string::npos)
546                 {
547                     endPos = line.size();
548                 }
549                 startPos += argSearch.size();
550                 argValue = line.substr(startPos, endPos - startPos);
551                 return success;
552             }
553         }
554     }
555     return failure;
556 }
557 
558 int UserMgr::setPamModuleArgValue(const std::string &moduleName,
559                                   const std::string &argName,
560                                   const std::string &argValue)
561 {
562     std::string fileName;
563     if (moduleName == pamTally2)
564     {
565         fileName = pamAuthConfigFile;
566     }
567     else
568     {
569         fileName = pamPasswdConfigFile;
570     }
571     std::string tmpFileName = fileName + "_tmp";
572     std::ifstream fileToRead(fileName, std::ios::in);
573     std::ofstream fileToWrite(tmpFileName, std::ios::out);
574     if (!fileToRead.is_open() || !fileToWrite.is_open())
575     {
576         log<level::ERR>("Failed to open pam configuration /tmp file",
577                         entry("FILE_NAME=%s", fileName.c_str()));
578         return failure;
579     }
580     std::string line;
581     auto argSearch = argName + "=";
582     size_t startPos = 0;
583     size_t endPos = 0;
584     bool found = false;
585     while (getline(fileToRead, line))
586     {
587         // skip comments section starting with #
588         if ((startPos = line.find('#')) != std::string::npos)
589         {
590             if (startPos == 0)
591             {
592                 fileToWrite << line << std::endl;
593                 continue;
594             }
595             // skip comments after meaningful section and process those
596             line = line.substr(0, startPos);
597         }
598         if (line.find(moduleName) != std::string::npos)
599         {
600             if ((startPos = line.find(argSearch)) != std::string::npos)
601             {
602                 if ((endPos = line.find(' ', startPos)) == std::string::npos)
603                 {
604                     endPos = line.size();
605                 }
606                 startPos += argSearch.size();
607                 fileToWrite << line.substr(0, startPos) << argValue
608                             << line.substr(endPos, line.size() - endPos)
609                             << std::endl;
610                 found = true;
611                 continue;
612             }
613         }
614         fileToWrite << line << std::endl;
615     }
616     fileToWrite.close();
617     fileToRead.close();
618     if (found)
619     {
620         if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
621         {
622             return success;
623         }
624     }
625     return failure;
626 }
627 
628 void UserMgr::userEnable(const std::string &userName, bool enabled)
629 {
630     // All user management lock has to be based on /etc/shadow
631     phosphor::user::shadow::Lock lock();
632     throwForUserDoesNotExist(userName);
633     try
634     {
635         executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
636                    (enabled ? "" : "1970-01-02"));
637     }
638     catch (const InternalFailure &e)
639     {
640         log<level::ERR>("Unable to modify user enabled state");
641         elog<InternalFailure>();
642     }
643 
644     log<level::INFO>("User enabled/disabled state updated successfully",
645                      entry("USER_NAME=%s", userName.c_str()),
646                      entry("ENABLED=%d", enabled));
647     return;
648 }
649 
650 /**
651  * pam_tally2 app will provide the user failure count and failure status
652  * in second line of output with words position [0] - user name,
653  * [1] - failure count, [2] - latest timestamp, [3] - failure timestamp
654  * [4] - failure app
655  **/
656 
657 static constexpr size_t t2UserIdx = 0;
658 static constexpr size_t t2FailCntIdx = 1;
659 static constexpr size_t t2OutputIndex = 1;
660 
661 bool UserMgr::userLockedForFailedAttempt(const std::string &userName)
662 {
663     // All user management lock has to be based on /etc/shadow
664     phosphor::user::shadow::Lock lock();
665     std::vector<std::string> output;
666 
667     output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
668 
669     std::vector<std::string> splitWords;
670     boost::algorithm::split(splitWords, output[t2OutputIndex],
671                             boost::algorithm::is_any_of("\t "),
672                             boost::token_compress_on);
673 
674     if (splitWords[t2UserIdx] == userName)
675     {
676         try
677         {
678             unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
679             uint16_t value16 = 0;
680             if (tmp > std::numeric_limits<decltype(value16)>::max())
681             {
682                 throw std::out_of_range("Out of range");
683             }
684             value16 = static_cast<decltype(value16)>(tmp);
685             if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 &&
686                 value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout())
687             {
688                 return true; // User account is locked out
689             }
690             return false; // User account is un-locked
691         }
692         catch (const std::exception &e)
693         {
694             log<level::ERR>("Exception for userLockedForFailedAttempt",
695                             entry("WHAT=%s", e.what()));
696             throw;
697         }
698     }
699     log<level::ERR>("Unable to get user account failed attempt",
700                     entry("USER_NAME=%s", userName.c_str()));
701     elog<InternalFailure>();
702     return false;
703 }
704 
705 bool UserMgr::userLockedForFailedAttempt(const std::string &userName,
706                                          const bool &value)
707 {
708     // All user management lock has to be based on /etc/shadow
709     phosphor::user::shadow::Lock lock();
710     std::vector<std::string> output;
711     if (value == true)
712     {
713         return userLockedForFailedAttempt(userName);
714     }
715     output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
716 
717     std::vector<std::string> splitWords;
718     boost::algorithm::split(splitWords, output[t2OutputIndex],
719                             boost::algorithm::is_any_of("\t "),
720                             boost::token_compress_on);
721 
722     if (splitWords[t2UserIdx] == userName)
723     {
724         return userLockedForFailedAttempt(userName);
725     }
726     log<level::ERR>("Unable to clear user account failed attempt");
727     elog<InternalFailure>();
728     return false;
729 }
730 
731 UserSSHLists UserMgr::getUserAndSshGrpList()
732 {
733     // All user management lock has to be based on /etc/shadow
734     phosphor::user::shadow::Lock lock();
735 
736     std::vector<std::string> userList;
737     std::vector<std::string> sshUsersList;
738     struct passwd pw, *pwp = nullptr;
739     std::array<char, 1024> buffer{};
740 
741     phosphor::user::File passwd(passwdFileName, "r");
742     if ((passwd)() == NULL)
743     {
744         log<level::ERR>("Error opening the passwd file");
745         elog<InternalFailure>();
746     }
747 
748     while (true)
749     {
750         auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
751                              &pwp);
752         if ((r != 0) || (pwp == NULL))
753         {
754             // Any error, break the loop.
755             break;
756         }
757         // Add all users whose UID >= 1000 and < 65534
758         // and special UID 0.
759         if ((pwp->pw_uid == 0) ||
760             ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
761         {
762             std::string userName(pwp->pw_name);
763             userList.emplace_back(userName);
764 
765             // ssh doesn't have separate group. Check login shell entry to
766             // get all users list which are member of ssh group.
767             std::string loginShell(pwp->pw_shell);
768             if (loginShell == "/bin/sh")
769             {
770                 sshUsersList.emplace_back(userName);
771             }
772         }
773     }
774     endpwent();
775     return std::make_pair(std::move(userList), std::move(sshUsersList));
776 }
777 
778 size_t UserMgr::getIpmiUsersCount()
779 {
780     std::vector<std::string> userList = getUsersInGroup("ipmi");
781     return userList.size();
782 }
783 
784 bool UserMgr::isUserEnabled(const std::string &userName)
785 {
786     // All user management lock has to be based on /etc/shadow
787     phosphor::user::shadow::Lock lock();
788     std::array<char, 4096> buffer{};
789     struct spwd spwd;
790     struct spwd *resultPtr = nullptr;
791     int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
792                             buffer.max_size(), &resultPtr);
793     if (!status && (&spwd == resultPtr))
794     {
795         if (resultPtr->sp_expire >= 0)
796         {
797             return false; // user locked out
798         }
799         return true;
800     }
801     return false; // assume user is disabled for any error.
802 }
803 
804 std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName)
805 {
806     std::vector<std::string> usersInGroup;
807     // Should be more than enough to get the pwd structure.
808     std::array<char, 4096> buffer{};
809     struct group grp;
810     struct group *resultPtr = nullptr;
811 
812     int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
813                             buffer.max_size(), &resultPtr);
814 
815     if (!status && (&grp == resultPtr))
816     {
817         for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
818         {
819             usersInGroup.emplace_back(*(grp.gr_mem));
820         }
821     }
822     else
823     {
824         log<level::ERR>("Group not found",
825                         entry("GROUP=%s", groupName.c_str()));
826         // Don't throw error, just return empty userList - fallback
827     }
828     return usersInGroup;
829 }
830 
831 void UserMgr::initUserObjects(void)
832 {
833     // All user management lock has to be based on /etc/shadow
834     phosphor::user::shadow::Lock lock();
835     std::vector<std::string> userNameList;
836     std::vector<std::string> sshGrpUsersList;
837     UserSSHLists userSSHLists = getUserAndSshGrpList();
838     userNameList = std::move(userSSHLists.first);
839     sshGrpUsersList = std::move(userSSHLists.second);
840 
841     if (!userNameList.empty())
842     {
843         std::map<std::string, std::vector<std::string>> groupLists;
844         for (auto &grp : groupsMgr)
845         {
846             if (grp == grpSsh)
847             {
848                 groupLists.emplace(grp, sshGrpUsersList);
849             }
850             else
851             {
852                 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
853                 groupLists.emplace(grp, grpUsersList);
854             }
855         }
856         for (auto &grp : privMgr)
857         {
858             std::vector<std::string> grpUsersList = getUsersInGroup(grp);
859             groupLists.emplace(grp, grpUsersList);
860         }
861 
862         for (auto &user : userNameList)
863         {
864             std::vector<std::string> userGroups;
865             std::string userPriv;
866             for (const auto &grp : groupLists)
867             {
868                 std::vector<std::string> tempGrp = grp.second;
869                 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
870                     tempGrp.end())
871                 {
872                     if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
873                         privMgr.end())
874                     {
875                         userPriv = grp.first;
876                     }
877                     else
878                     {
879                         userGroups.emplace_back(grp.first);
880                     }
881                 }
882             }
883             // Add user objects to the Users path.
884             auto objPath = std::string(usersObjPath) + "/" + user;
885             std::sort(userGroups.begin(), userGroups.end());
886             usersList.emplace(user,
887                               std::move(std::make_unique<phosphor::user::Users>(
888                                   bus, objPath.c_str(), userGroups, userPriv,
889                                   isUserEnabled(user), *this)));
890         }
891     }
892 }
893 
894 UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) :
895     UserMgrIface(bus, path), AccountPolicyIface(bus, path), bus(bus), path(path)
896 {
897     UserMgrIface::allPrivileges(privMgr);
898     std::sort(groupsMgr.begin(), groupsMgr.end());
899     UserMgrIface::allGroups(groupsMgr);
900     std::string valueStr;
901     auto value = minPasswdLength;
902     unsigned long tmp = 0;
903     if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
904         success)
905     {
906         AccountPolicyIface::minPasswordLength(minPasswdLength);
907     }
908     else
909     {
910         try
911         {
912             tmp = std::stoul(valueStr, nullptr);
913             if (tmp > std::numeric_limits<decltype(value)>::max())
914             {
915                 throw std::out_of_range("Out of range");
916             }
917             value = static_cast<decltype(value)>(tmp);
918         }
919         catch (const std::exception &e)
920         {
921             log<level::ERR>("Exception for MinPasswordLength",
922                             entry("WHAT=%s", e.what()));
923             throw;
924         }
925         AccountPolicyIface::minPasswordLength(value);
926     }
927     valueStr.clear();
928     if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
929         success)
930     {
931         AccountPolicyIface::rememberOldPasswordTimes(0);
932     }
933     else
934     {
935         value = 0;
936         try
937         {
938             tmp = std::stoul(valueStr, nullptr);
939             if (tmp > std::numeric_limits<decltype(value)>::max())
940             {
941                 throw std::out_of_range("Out of range");
942             }
943             value = static_cast<decltype(value)>(tmp);
944         }
945         catch (const std::exception &e)
946         {
947             log<level::ERR>("Exception for RememberOldPasswordTimes",
948                             entry("WHAT=%s", e.what()));
949             throw;
950         }
951         AccountPolicyIface::rememberOldPasswordTimes(value);
952     }
953     valueStr.clear();
954     if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
955     {
956         AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
957     }
958     else
959     {
960         uint16_t value16 = 0;
961         try
962         {
963             tmp = std::stoul(valueStr, nullptr);
964             if (tmp > std::numeric_limits<decltype(value16)>::max())
965             {
966                 throw std::out_of_range("Out of range");
967             }
968             value16 = static_cast<decltype(value16)>(tmp);
969         }
970         catch (const std::exception &e)
971         {
972             log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
973                             entry("WHAT=%s", e.what()));
974             throw;
975         }
976         AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
977     }
978     valueStr.clear();
979     if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
980     {
981         AccountPolicyIface::accountUnlockTimeout(0);
982     }
983     else
984     {
985         uint32_t value32 = 0;
986         try
987         {
988             tmp = std::stoul(valueStr, nullptr);
989             if (tmp > std::numeric_limits<decltype(value32)>::max())
990             {
991                 throw std::out_of_range("Out of range");
992             }
993             value32 = static_cast<decltype(value32)>(tmp);
994         }
995         catch (const std::exception &e)
996         {
997             log<level::ERR>("Exception for AccountUnlockTimeout",
998                             entry("WHAT=%s", e.what()));
999             throw;
1000         }
1001         AccountPolicyIface::accountUnlockTimeout(value32);
1002     }
1003     initUserObjects();
1004 }
1005 
1006 } // namespace user
1007 } // namespace phosphor
1008