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