xref: /openbmc/phosphor-user-manager/user_mgr.cpp (revision 69570e5eb4114348ab6897a62138fe58336fb53f)
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 t2UserIdx = 0;
690 static constexpr size_t t2FailCntIdx = 1;
691 static constexpr size_t t2FailDateIdx = 2;
692 static constexpr size_t t2FailTimeIdx = 3;
693 static constexpr size_t t2OutputIndex = 1;
694 
695 bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
696 {
697     // All user management lock has to be based on /etc/shadow
698     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
699     if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
700     {
701         return false;
702     }
703 
704     std::vector<std::string> output;
705     try
706     {
707         output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
708     }
709     catch (const InternalFailure& e)
710     {
711         log<level::ERR>("Unable to read login failure counter");
712         elog<InternalFailure>();
713     }
714 
715     std::vector<std::string> splitWords;
716     boost::algorithm::split(splitWords, output[t2OutputIndex],
717                             boost::algorithm::is_any_of("\t "),
718                             boost::token_compress_on);
719 
720     uint16_t failAttempts = 0;
721     try
722     {
723         unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
724         if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
725         {
726             throw std::out_of_range("Out of range");
727         }
728         failAttempts = static_cast<decltype(failAttempts)>(tmp);
729     }
730     catch (const std::exception& e)
731     {
732         log<level::ERR>("Exception for userLockedForFailedAttempt",
733                         entry("WHAT=%s", e.what()));
734         elog<InternalFailure>();
735     }
736 
737     if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
738     {
739         return false;
740     }
741 
742     // When failedAttempts is not 0, Latest failure date/time should be
743     // available
744     if (splitWords.size() < 4)
745     {
746         log<level::ERR>("Unable to read latest failure date/time");
747         elog<InternalFailure>();
748     }
749 
750     const std::string failDateTime =
751         splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
752 
753     // NOTE: Cannot use std::get_time() here as the implementation of %y in
754     // libstdc++ does not match POSIX strptime() before gcc 12.1.0
755     // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
756     std::tm tmStruct = {};
757     if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
758     {
759         log<level::ERR>("Failed to parse latest failure date/time");
760         elog<InternalFailure>();
761     }
762 
763     time_t failTimestamp = std::mktime(&tmStruct);
764     if (failTimestamp +
765             static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
766         std::time(NULL))
767     {
768         return false;
769     }
770 
771     return true;
772 }
773 
774 bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
775                                          const bool& value)
776 {
777     // All user management lock has to be based on /etc/shadow
778     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
779     if (value == true)
780     {
781         return userLockedForFailedAttempt(userName);
782     }
783 
784     try
785     {
786         executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
787     }
788     catch (const InternalFailure& e)
789     {
790         log<level::ERR>("Unable to reset login failure counter");
791         elog<InternalFailure>();
792     }
793 
794     return userLockedForFailedAttempt(userName);
795 }
796 
797 bool UserMgr::userPasswordExpired(const std::string& userName)
798 {
799     // All user management lock has to be based on /etc/shadow
800     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
801 
802     struct spwd spwd
803     {};
804     struct spwd* spwdPtr = nullptr;
805     auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
806     if (buflen < -1)
807     {
808         // Use a default size if there is no hard limit suggested by sysconf()
809         buflen = 1024;
810     }
811     std::vector<char> buffer(buflen);
812     auto status =
813         getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
814     // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
815     // If no matching password record was found, these functions return 0
816     // and store NULL in *spwdPtr
817     if ((status == 0) && (&spwd == spwdPtr))
818     {
819         // Determine password validity per "chage" docs, where:
820         //   spwd.sp_lstchg == 0 means password is expired, and
821         //   spwd.sp_max == -1 means the password does not expire.
822         constexpr long seconds_per_day = 60 * 60 * 24;
823         long today = static_cast<long>(time(NULL)) / seconds_per_day;
824         if ((spwd.sp_lstchg == 0) ||
825             ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
826         {
827             return true;
828         }
829     }
830     else
831     {
832         // User entry is missing in /etc/shadow, indicating no SHA password.
833         // Treat this as new user without password entry in /etc/shadow
834         // TODO: Add property to indicate user password was not set yet
835         // https://github.com/openbmc/phosphor-user-manager/issues/8
836         return false;
837     }
838 
839     return false;
840 }
841 
842 UserSSHLists UserMgr::getUserAndSshGrpList()
843 {
844     // All user management lock has to be based on /etc/shadow
845     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
846 
847     std::vector<std::string> userList;
848     std::vector<std::string> sshUsersList;
849     struct passwd pw, *pwp = nullptr;
850     std::array<char, 1024> buffer{};
851 
852     phosphor::user::File passwd(passwdFileName, "r");
853     if ((passwd)() == NULL)
854     {
855         log<level::ERR>("Error opening the passwd file");
856         elog<InternalFailure>();
857     }
858 
859     while (true)
860     {
861         auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
862                              &pwp);
863         if ((r != 0) || (pwp == NULL))
864         {
865             // Any error, break the loop.
866             break;
867         }
868 #ifdef ENABLE_ROOT_USER_MGMT
869         // Add all users whose UID >= 1000 and < 65534
870         // and special UID 0.
871         if ((pwp->pw_uid == 0) ||
872             ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
873 #else
874         // Add all users whose UID >=1000 and < 65534
875         if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
876 #endif
877         {
878             std::string userName(pwp->pw_name);
879             userList.emplace_back(userName);
880 
881             // ssh doesn't have separate group. Check login shell entry to
882             // get all users list which are member of ssh group.
883             std::string loginShell(pwp->pw_shell);
884             if (loginShell == "/bin/sh")
885             {
886                 sshUsersList.emplace_back(userName);
887             }
888         }
889     }
890     endpwent();
891     return std::make_pair(std::move(userList), std::move(sshUsersList));
892 }
893 
894 size_t UserMgr::getIpmiUsersCount()
895 {
896     std::vector<std::string> userList = getUsersInGroup("ipmi");
897     return userList.size();
898 }
899 
900 bool UserMgr::isUserEnabled(const std::string& userName)
901 {
902     // All user management lock has to be based on /etc/shadow
903     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
904     std::array<char, 4096> buffer{};
905     struct spwd spwd;
906     struct spwd* resultPtr = nullptr;
907     int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
908                             buffer.max_size(), &resultPtr);
909     if (!status && (&spwd == resultPtr))
910     {
911         if (resultPtr->sp_expire >= 0)
912         {
913             return false; // user locked out
914         }
915         return true;
916     }
917     return false; // assume user is disabled for any error.
918 }
919 
920 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
921 {
922     std::vector<std::string> usersInGroup;
923     // Should be more than enough to get the pwd structure.
924     std::array<char, 4096> buffer{};
925     struct group grp;
926     struct group* resultPtr = nullptr;
927 
928     int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
929                             buffer.max_size(), &resultPtr);
930 
931     if (!status && (&grp == resultPtr))
932     {
933         for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
934         {
935             usersInGroup.emplace_back(*(grp.gr_mem));
936         }
937     }
938     else
939     {
940         log<level::ERR>("Group not found",
941                         entry("GROUP=%s", groupName.c_str()));
942         // Don't throw error, just return empty userList - fallback
943     }
944     return usersInGroup;
945 }
946 
947 DbusUserObj UserMgr::getPrivilegeMapperObject(void)
948 {
949     DbusUserObj objects;
950     try
951     {
952         std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
953         std::string interface = "xyz.openbmc_project.User.Ldap.Config";
954 
955         auto ldapMgmtService =
956             getServiceName(std::move(basePath), std::move(interface));
957         auto method = bus.new_method_call(
958             ldapMgmtService.c_str(), ldapMgrObjBasePath,
959             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
960 
961         auto reply = bus.call(method);
962         reply.read(objects);
963     }
964     catch (const InternalFailure& e)
965     {
966         log<level::ERR>("Unable to get the User Service",
967                         entry("WHAT=%s", e.what()));
968         throw;
969     }
970     catch (const sdbusplus::exception_t& e)
971     {
972         log<level::ERR>(
973             "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
974             entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
975         throw;
976     }
977     return objects;
978 }
979 
980 std::string UserMgr::getLdapGroupName(const std::string& userName)
981 {
982     struct passwd pwd
983     {};
984     struct passwd* pwdPtr = nullptr;
985     auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
986     if (buflen < -1)
987     {
988         // Use a default size if there is no hard limit suggested by sysconf()
989         buflen = 1024;
990     }
991     std::vector<char> buffer(buflen);
992     gid_t gid = 0;
993 
994     auto status =
995         getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
996     // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
997     // If no matching password record was found, these functions return 0
998     // and store NULL in *pwdPtr
999     if (!status && (&pwd == pwdPtr))
1000     {
1001         gid = pwd.pw_gid;
1002     }
1003     else
1004     {
1005         log<level::ERR>("User does not exist",
1006                         entry("USER_NAME=%s", userName.c_str()));
1007         elog<UserNameDoesNotExist>();
1008     }
1009 
1010     struct group* groups = nullptr;
1011     std::string ldapGroupName;
1012 
1013     while ((groups = getgrent()) != NULL)
1014     {
1015         if (groups->gr_gid == gid)
1016         {
1017             ldapGroupName = groups->gr_name;
1018             break;
1019         }
1020     }
1021     // Call endgrent() to close the group database.
1022     endgrent();
1023 
1024     return ldapGroupName;
1025 }
1026 
1027 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
1028 {
1029     auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1030                                           objMapperInterface, "GetObject");
1031 
1032     mapperCall.append(std::move(path));
1033     mapperCall.append(std::vector<std::string>({std::move(intf)}));
1034 
1035     auto mapperResponseMsg = bus.call(mapperCall);
1036 
1037     if (mapperResponseMsg.is_method_error())
1038     {
1039         log<level::ERR>("Error in mapper call");
1040         elog<InternalFailure>();
1041     }
1042 
1043     std::map<std::string, std::vector<std::string>> mapperResponse;
1044     mapperResponseMsg.read(mapperResponse);
1045 
1046     if (mapperResponse.begin() == mapperResponse.end())
1047     {
1048         log<level::ERR>("Invalid response from mapper");
1049         elog<InternalFailure>();
1050     }
1051 
1052     return mapperResponse.begin()->first;
1053 }
1054 
1055 UserInfoMap UserMgr::getUserInfo(std::string userName)
1056 {
1057     UserInfoMap userInfo;
1058     // Check whether the given user is local user or not.
1059     if (isUserExist(userName) == true)
1060     {
1061         const auto& user = usersList[userName];
1062         userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1063         userInfo.emplace("UserGroups", user.get()->userGroups());
1064         userInfo.emplace("UserEnabled", user.get()->userEnabled());
1065         userInfo.emplace("UserLockedForFailedAttempt",
1066                          user.get()->userLockedForFailedAttempt());
1067         userInfo.emplace("UserPasswordExpired",
1068                          user.get()->userPasswordExpired());
1069         userInfo.emplace("RemoteUser", false);
1070     }
1071     else
1072     {
1073         std::string ldapGroupName = getLdapGroupName(userName);
1074         if (ldapGroupName.empty())
1075         {
1076             log<level::ERR>("Unable to get group name",
1077                             entry("USER_NAME=%s", userName.c_str()));
1078             elog<InternalFailure>();
1079         }
1080 
1081         DbusUserObj objects = getPrivilegeMapperObject();
1082 
1083         std::string privilege;
1084         std::string groupName;
1085         std::string ldapConfigPath;
1086 
1087         try
1088         {
1089             for (const auto& obj : objects)
1090             {
1091                 for (const auto& interface : obj.second)
1092                 {
1093                     if ((interface.first ==
1094                          "xyz.openbmc_project.Object.Enable"))
1095                     {
1096                         for (const auto& property : interface.second)
1097                         {
1098                             auto value = std::get<bool>(property.second);
1099                             if ((property.first == "Enabled") &&
1100                                 (value == true))
1101                             {
1102                                 ldapConfigPath = obj.first;
1103                                 break;
1104                             }
1105                         }
1106                     }
1107                 }
1108                 if (!ldapConfigPath.empty())
1109                 {
1110                     break;
1111                 }
1112             }
1113 
1114             if (ldapConfigPath.empty())
1115             {
1116                 return userInfo;
1117             }
1118 
1119             for (const auto& obj : objects)
1120             {
1121                 for (const auto& interface : obj.second)
1122                 {
1123                     if ((interface.first ==
1124                          "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1125                         (obj.first.str.find(ldapConfigPath) !=
1126                          std::string::npos))
1127                     {
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                             if (groupName == ldapGroupName)
1141                             {
1142                                 userInfo["UserPrivilege"] = privilege;
1143                             }
1144                         }
1145                     }
1146                 }
1147             }
1148             auto priv = std::get<std::string>(userInfo["UserPrivilege"]);
1149 
1150             if (priv.empty())
1151             {
1152                 log<level::ERR>("LDAP group privilege mapping does not exist");
1153             }
1154         }
1155         catch (const std::bad_variant_access& e)
1156         {
1157             log<level::ERR>("Error while accessing variant",
1158                             entry("WHAT=%s", e.what()));
1159             elog<InternalFailure>();
1160         }
1161         userInfo.emplace("RemoteUser", true);
1162     }
1163 
1164     return userInfo;
1165 }
1166 
1167 void UserMgr::initUserObjects(void)
1168 {
1169     // All user management lock has to be based on /etc/shadow
1170     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1171     std::vector<std::string> userNameList;
1172     std::vector<std::string> sshGrpUsersList;
1173     UserSSHLists userSSHLists = getUserAndSshGrpList();
1174     userNameList = std::move(userSSHLists.first);
1175     sshGrpUsersList = std::move(userSSHLists.second);
1176 
1177     if (!userNameList.empty())
1178     {
1179         std::map<std::string, std::vector<std::string>> groupLists;
1180         for (auto& grp : groupsMgr)
1181         {
1182             if (grp == grpSsh)
1183             {
1184                 groupLists.emplace(grp, sshGrpUsersList);
1185             }
1186             else
1187             {
1188                 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1189                 groupLists.emplace(grp, grpUsersList);
1190             }
1191         }
1192         for (auto& grp : privMgr)
1193         {
1194             std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1195             groupLists.emplace(grp, grpUsersList);
1196         }
1197 
1198         for (auto& user : userNameList)
1199         {
1200             std::vector<std::string> userGroups;
1201             std::string userPriv;
1202             for (const auto& grp : groupLists)
1203             {
1204                 std::vector<std::string> tempGrp = grp.second;
1205                 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1206                     tempGrp.end())
1207                 {
1208                     if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1209                         privMgr.end())
1210                     {
1211                         userPriv = grp.first;
1212                     }
1213                     else
1214                     {
1215                         userGroups.emplace_back(grp.first);
1216                     }
1217                 }
1218             }
1219             // Add user objects to the Users path.
1220             sdbusplus::message::object_path tempObjPath(usersObjPath);
1221             tempObjPath /= user;
1222             std::string objPath(tempObjPath);
1223             std::sort(userGroups.begin(), userGroups.end());
1224             usersList.emplace(user,
1225                               std::move(std::make_unique<phosphor::user::Users>(
1226                                   bus, objPath.c_str(), userGroups, userPriv,
1227                                   isUserEnabled(user), *this)));
1228         }
1229     }
1230 }
1231 
1232 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1233     Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path)
1234 {
1235     UserMgrIface::allPrivileges(privMgr);
1236     std::sort(groupsMgr.begin(), groupsMgr.end());
1237     UserMgrIface::allGroups(groupsMgr);
1238     std::string valueStr;
1239     auto value = minPasswdLength;
1240     unsigned long tmp = 0;
1241     if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1242         success)
1243     {
1244         AccountPolicyIface::minPasswordLength(minPasswdLength);
1245     }
1246     else
1247     {
1248         try
1249         {
1250             tmp = std::stoul(valueStr, nullptr);
1251             if (tmp > std::numeric_limits<decltype(value)>::max())
1252             {
1253                 throw std::out_of_range("Out of range");
1254             }
1255             value = static_cast<decltype(value)>(tmp);
1256         }
1257         catch (const std::exception& e)
1258         {
1259             log<level::ERR>("Exception for MinPasswordLength",
1260                             entry("WHAT=%s", e.what()));
1261             throw;
1262         }
1263         AccountPolicyIface::minPasswordLength(value);
1264     }
1265     valueStr.clear();
1266     if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1267         success)
1268     {
1269         AccountPolicyIface::rememberOldPasswordTimes(0);
1270     }
1271     else
1272     {
1273         value = 0;
1274         try
1275         {
1276             tmp = std::stoul(valueStr, nullptr);
1277             if (tmp > std::numeric_limits<decltype(value)>::max())
1278             {
1279                 throw std::out_of_range("Out of range");
1280             }
1281             value = static_cast<decltype(value)>(tmp);
1282         }
1283         catch (const std::exception& e)
1284         {
1285             log<level::ERR>("Exception for RememberOldPasswordTimes",
1286                             entry("WHAT=%s", e.what()));
1287             throw;
1288         }
1289         AccountPolicyIface::rememberOldPasswordTimes(value);
1290     }
1291     valueStr.clear();
1292     if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1293     {
1294         AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1295     }
1296     else
1297     {
1298         uint16_t value16 = 0;
1299         try
1300         {
1301             tmp = std::stoul(valueStr, nullptr);
1302             if (tmp > std::numeric_limits<decltype(value16)>::max())
1303             {
1304                 throw std::out_of_range("Out of range");
1305             }
1306             value16 = static_cast<decltype(value16)>(tmp);
1307         }
1308         catch (const std::exception& e)
1309         {
1310             log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1311                             entry("WHAT=%s", e.what()));
1312             throw;
1313         }
1314         AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1315     }
1316     valueStr.clear();
1317     if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1318     {
1319         AccountPolicyIface::accountUnlockTimeout(0);
1320     }
1321     else
1322     {
1323         uint32_t value32 = 0;
1324         try
1325         {
1326             tmp = std::stoul(valueStr, nullptr);
1327             if (tmp > std::numeric_limits<decltype(value32)>::max())
1328             {
1329                 throw std::out_of_range("Out of range");
1330             }
1331             value32 = static_cast<decltype(value32)>(tmp);
1332         }
1333         catch (const std::exception& e)
1334         {
1335             log<level::ERR>("Exception for AccountUnlockTimeout",
1336                             entry("WHAT=%s", e.what()));
1337             throw;
1338         }
1339         AccountPolicyIface::accountUnlockTimeout(value32);
1340     }
1341     initUserObjects();
1342 
1343     // emit the signal
1344     this->emit_object_added();
1345 }
1346 
1347 } // namespace user
1348 } // namespace phosphor
1349