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