xref: /openbmc/phosphor-user-manager/user_mgr.cpp (revision 0b1ad3d8cbf0ef3d95494603d351de0ed3463647)
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         return value;
473     }
474     if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
475                              std::to_string(value)) != success)
476     {
477         log<level::ERR>("Unable to set minPasswordLength");
478         elog<InternalFailure>();
479     }
480     return AccountPolicyIface::minPasswordLength(value);
481 }
482 
483 uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
484 {
485     if (value == AccountPolicyIface::rememberOldPasswordTimes())
486     {
487         return value;
488     }
489     if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
490                              std::to_string(value)) != success)
491     {
492         log<level::ERR>("Unable to set rememberOldPasswordTimes");
493         elog<InternalFailure>();
494     }
495     return AccountPolicyIface::rememberOldPasswordTimes(value);
496 }
497 
498 uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
499 {
500     if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
501     {
502         return value;
503     }
504     if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
505                              std::to_string(value)) != success)
506     {
507         log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
508         elog<InternalFailure>();
509     }
510     return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
511 }
512 
513 uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
514 {
515     if (value == AccountPolicyIface::accountUnlockTimeout())
516     {
517         return value;
518     }
519     if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
520         success)
521     {
522         log<level::ERR>("Unable to set accountUnlockTimeout");
523         elog<InternalFailure>();
524     }
525     return AccountPolicyIface::accountUnlockTimeout(value);
526 }
527 
528 int UserMgr::getPamModuleArgValue(const std::string& moduleName,
529                                   const std::string& argName,
530                                   std::string& argValue)
531 {
532     std::string fileName;
533     if (moduleName == pamTally2)
534     {
535         fileName = pamAuthConfigFile;
536     }
537     else
538     {
539         fileName = pamPasswdConfigFile;
540     }
541     std::ifstream fileToRead(fileName, std::ios::in);
542     if (!fileToRead.is_open())
543     {
544         log<level::ERR>("Failed to open pam configuration file",
545                         entry("FILE_NAME=%s", fileName.c_str()));
546         return failure;
547     }
548     std::string line;
549     auto argSearch = argName + "=";
550     size_t startPos = 0;
551     size_t endPos = 0;
552     while (getline(fileToRead, line))
553     {
554         // skip comments section starting with #
555         if ((startPos = line.find('#')) != std::string::npos)
556         {
557             if (startPos == 0)
558             {
559                 continue;
560             }
561             // skip comments after meaningful section and process those
562             line = line.substr(0, startPos);
563         }
564         if (line.find(moduleName) != std::string::npos)
565         {
566             if ((startPos = line.find(argSearch)) != std::string::npos)
567             {
568                 if ((endPos = line.find(' ', startPos)) == std::string::npos)
569                 {
570                     endPos = line.size();
571                 }
572                 startPos += argSearch.size();
573                 argValue = line.substr(startPos, endPos - startPos);
574                 return success;
575             }
576         }
577     }
578     return failure;
579 }
580 
581 int UserMgr::setPamModuleArgValue(const std::string& moduleName,
582                                   const std::string& argName,
583                                   const std::string& argValue)
584 {
585     std::string fileName;
586     if (moduleName == pamTally2)
587     {
588         fileName = pamAuthConfigFile;
589     }
590     else
591     {
592         fileName = pamPasswdConfigFile;
593     }
594     std::string tmpFileName = fileName + "_tmp";
595     std::ifstream fileToRead(fileName, std::ios::in);
596     std::ofstream fileToWrite(tmpFileName, std::ios::out);
597     if (!fileToRead.is_open() || !fileToWrite.is_open())
598     {
599         log<level::ERR>("Failed to open pam configuration /tmp file",
600                         entry("FILE_NAME=%s", fileName.c_str()));
601         return failure;
602     }
603     std::string line;
604     auto argSearch = argName + "=";
605     size_t startPos = 0;
606     size_t endPos = 0;
607     bool found = false;
608     while (getline(fileToRead, line))
609     {
610         // skip comments section starting with #
611         if ((startPos = line.find('#')) != std::string::npos)
612         {
613             if (startPos == 0)
614             {
615                 fileToWrite << line << std::endl;
616                 continue;
617             }
618             // skip comments after meaningful section and process those
619             line = line.substr(0, startPos);
620         }
621         if (line.find(moduleName) != std::string::npos)
622         {
623             if ((startPos = line.find(argSearch)) != std::string::npos)
624             {
625                 if ((endPos = line.find(' ', startPos)) == std::string::npos)
626                 {
627                     endPos = line.size();
628                 }
629                 startPos += argSearch.size();
630                 fileToWrite << line.substr(0, startPos) << argValue
631                             << line.substr(endPos, line.size() - endPos)
632                             << std::endl;
633                 found = true;
634                 continue;
635             }
636         }
637         fileToWrite << line << std::endl;
638     }
639     fileToWrite.close();
640     fileToRead.close();
641     if (found)
642     {
643         if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
644         {
645             return success;
646         }
647     }
648     return failure;
649 }
650 
651 void UserMgr::userEnable(const std::string& userName, bool enabled)
652 {
653     // All user management lock has to be based on /etc/shadow
654     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
655     throwForUserDoesNotExist(userName);
656     try
657     {
658         // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
659         // 1970-01-01, that's an implementation-defined behavior
660         executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
661                    (enabled ? "" : "1970-01-01"));
662     }
663     catch (const InternalFailure& e)
664     {
665         log<level::ERR>("Unable to modify user enabled state");
666         elog<InternalFailure>();
667     }
668 
669     log<level::INFO>("User enabled/disabled state updated successfully",
670                      entry("USER_NAME=%s", userName.c_str()),
671                      entry("ENABLED=%d", enabled));
672     return;
673 }
674 
675 /**
676  * pam_tally2 app will provide the user failure count and failure status
677  * in second line of output with words position [0] - user name,
678  * [1] - failure count, [2] - latest timestamp, [3] - failure timestamp
679  * [4] - failure app
680  **/
681 
682 static constexpr size_t t2UserIdx = 0;
683 static constexpr size_t t2FailCntIdx = 1;
684 static constexpr size_t t2OutputIndex = 1;
685 
686 bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
687 {
688     // All user management lock has to be based on /etc/shadow
689     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
690     std::vector<std::string> output;
691 
692     output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
693 
694     std::vector<std::string> splitWords;
695     boost::algorithm::split(splitWords, output[t2OutputIndex],
696                             boost::algorithm::is_any_of("\t "),
697                             boost::token_compress_on);
698 
699     try
700     {
701         unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
702         uint16_t value16 = 0;
703         if (tmp > std::numeric_limits<decltype(value16)>::max())
704         {
705             throw std::out_of_range("Out of range");
706         }
707         value16 = static_cast<decltype(value16)>(tmp);
708         if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 &&
709             value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout())
710         {
711             return true; // User account is locked out
712         }
713         return false; // User account is un-locked
714     }
715     catch (const std::exception& e)
716     {
717         log<level::ERR>("Exception for userLockedForFailedAttempt",
718                         entry("WHAT=%s", e.what()));
719         throw;
720     }
721 }
722 
723 bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
724                                          const bool& value)
725 {
726     // All user management lock has to be based on /etc/shadow
727     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
728     std::vector<std::string> output;
729     if (value == true)
730     {
731         return userLockedForFailedAttempt(userName);
732     }
733     output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
734 
735     std::vector<std::string> splitWords;
736     boost::algorithm::split(splitWords, output[t2OutputIndex],
737                             boost::algorithm::is_any_of("\t "),
738                             boost::token_compress_on);
739 
740     return userLockedForFailedAttempt(userName);
741 }
742 
743 bool UserMgr::userPasswordExpired(const std::string& userName)
744 {
745     // All user management lock has to be based on /etc/shadow
746     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
747 
748     struct spwd spwd
749     {};
750     struct spwd* spwdPtr = nullptr;
751     auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
752     if (buflen < -1)
753     {
754         // Use a default size if there is no hard limit suggested by sysconf()
755         buflen = 1024;
756     }
757     std::vector<char> buffer(buflen);
758     auto status =
759         getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
760     // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
761     // If no matching password record was found, these functions return 0
762     // and store NULL in *spwdPtr
763     if ((status == 0) && (&spwd == spwdPtr))
764     {
765         // Determine password validity per "chage" docs, where:
766         //   spwd.sp_lstchg == 0 means password is expired, and
767         //   spwd.sp_max == -1 means the password does not expire.
768         constexpr long seconds_per_day = 60 * 60 * 24;
769         long today = static_cast<long>(time(NULL)) / seconds_per_day;
770         if ((spwd.sp_lstchg == 0) ||
771             ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
772         {
773             return true;
774         }
775     }
776     else
777     {
778         // User entry is missing in /etc/shadow, indicating no SHA password.
779         // Treat this as new user without password entry in /etc/shadow
780         // TODO: Add property to indicate user password was not set yet
781         // https://github.com/openbmc/phosphor-user-manager/issues/8
782         return false;
783     }
784 
785     return false;
786 }
787 
788 UserSSHLists UserMgr::getUserAndSshGrpList()
789 {
790     // All user management lock has to be based on /etc/shadow
791     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
792 
793     std::vector<std::string> userList;
794     std::vector<std::string> sshUsersList;
795     struct passwd pw, *pwp = nullptr;
796     std::array<char, 1024> buffer{};
797 
798     phosphor::user::File passwd(passwdFileName, "r");
799     if ((passwd)() == NULL)
800     {
801         log<level::ERR>("Error opening the passwd file");
802         elog<InternalFailure>();
803     }
804 
805     while (true)
806     {
807         auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
808                              &pwp);
809         if ((r != 0) || (pwp == NULL))
810         {
811             // Any error, break the loop.
812             break;
813         }
814 #ifdef ENABLE_ROOT_USER_MGMT
815         // Add all users whose UID >= 1000 and < 65534
816         // and special UID 0.
817         if ((pwp->pw_uid == 0) ||
818             ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
819 #else
820         // Add all users whose UID >=1000 and < 65534
821         if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
822 #endif
823         {
824             std::string userName(pwp->pw_name);
825             userList.emplace_back(userName);
826 
827             // ssh doesn't have separate group. Check login shell entry to
828             // get all users list which are member of ssh group.
829             std::string loginShell(pwp->pw_shell);
830             if (loginShell == "/bin/sh")
831             {
832                 sshUsersList.emplace_back(userName);
833             }
834         }
835     }
836     endpwent();
837     return std::make_pair(std::move(userList), std::move(sshUsersList));
838 }
839 
840 size_t UserMgr::getIpmiUsersCount()
841 {
842     std::vector<std::string> userList = getUsersInGroup("ipmi");
843     return userList.size();
844 }
845 
846 bool UserMgr::isUserEnabled(const std::string& userName)
847 {
848     // All user management lock has to be based on /etc/shadow
849     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
850     std::array<char, 4096> buffer{};
851     struct spwd spwd;
852     struct spwd* resultPtr = nullptr;
853     int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
854                             buffer.max_size(), &resultPtr);
855     if (!status && (&spwd == resultPtr))
856     {
857         if (resultPtr->sp_expire >= 0)
858         {
859             return false; // user locked out
860         }
861         return true;
862     }
863     return false; // assume user is disabled for any error.
864 }
865 
866 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
867 {
868     std::vector<std::string> usersInGroup;
869     // Should be more than enough to get the pwd structure.
870     std::array<char, 4096> buffer{};
871     struct group grp;
872     struct group* resultPtr = nullptr;
873 
874     int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
875                             buffer.max_size(), &resultPtr);
876 
877     if (!status && (&grp == resultPtr))
878     {
879         for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
880         {
881             usersInGroup.emplace_back(*(grp.gr_mem));
882         }
883     }
884     else
885     {
886         log<level::ERR>("Group not found",
887                         entry("GROUP=%s", groupName.c_str()));
888         // Don't throw error, just return empty userList - fallback
889     }
890     return usersInGroup;
891 }
892 
893 DbusUserObj UserMgr::getPrivilegeMapperObject(void)
894 {
895     DbusUserObj objects;
896     try
897     {
898         std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
899         std::string interface = "xyz.openbmc_project.User.Ldap.Config";
900 
901         auto ldapMgmtService =
902             getServiceName(std::move(basePath), std::move(interface));
903         auto method = bus.new_method_call(
904             ldapMgmtService.c_str(), ldapMgrObjBasePath,
905             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
906 
907         auto reply = bus.call(method);
908         reply.read(objects);
909     }
910     catch (const InternalFailure& e)
911     {
912         log<level::ERR>("Unable to get the User Service",
913                         entry("WHAT=%s", e.what()));
914         throw;
915     }
916     catch (const sdbusplus::exception::exception& e)
917     {
918         log<level::ERR>(
919             "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
920             entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
921         throw;
922     }
923     return objects;
924 }
925 
926 std::string UserMgr::getLdapGroupName(const std::string& userName)
927 {
928     struct passwd pwd
929     {};
930     struct passwd* pwdPtr = nullptr;
931     auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
932     if (buflen < -1)
933     {
934         // Use a default size if there is no hard limit suggested by sysconf()
935         buflen = 1024;
936     }
937     std::vector<char> buffer(buflen);
938     gid_t gid = 0;
939 
940     auto status =
941         getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
942     // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
943     // If no matching password record was found, these functions return 0
944     // and store NULL in *pwdPtr
945     if (!status && (&pwd == pwdPtr))
946     {
947         gid = pwd.pw_gid;
948     }
949     else
950     {
951         log<level::ERR>("User does not exist",
952                         entry("USER_NAME=%s", userName.c_str()));
953         elog<UserNameDoesNotExist>();
954     }
955 
956     struct group* groups = nullptr;
957     std::string ldapGroupName;
958 
959     while ((groups = getgrent()) != NULL)
960     {
961         if (groups->gr_gid == gid)
962         {
963             ldapGroupName = groups->gr_name;
964             break;
965         }
966     }
967     // Call endgrent() to close the group database.
968     endgrent();
969 
970     return ldapGroupName;
971 }
972 
973 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
974 {
975     auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
976                                           objMapperInterface, "GetObject");
977 
978     mapperCall.append(std::move(path));
979     mapperCall.append(std::vector<std::string>({std::move(intf)}));
980 
981     auto mapperResponseMsg = bus.call(mapperCall);
982 
983     if (mapperResponseMsg.is_method_error())
984     {
985         log<level::ERR>("Error in mapper call");
986         elog<InternalFailure>();
987     }
988 
989     std::map<std::string, std::vector<std::string>> mapperResponse;
990     mapperResponseMsg.read(mapperResponse);
991 
992     if (mapperResponse.begin() == mapperResponse.end())
993     {
994         log<level::ERR>("Invalid response from mapper");
995         elog<InternalFailure>();
996     }
997 
998     return mapperResponse.begin()->first;
999 }
1000 
1001 UserInfoMap UserMgr::getUserInfo(std::string userName)
1002 {
1003     UserInfoMap userInfo;
1004     // Check whether the given user is local user or not.
1005     if (isUserExist(userName) == true)
1006     {
1007         const auto& user = usersList[userName];
1008         userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1009         userInfo.emplace("UserGroups", user.get()->userGroups());
1010         userInfo.emplace("UserEnabled", user.get()->userEnabled());
1011         userInfo.emplace("UserLockedForFailedAttempt",
1012                          user.get()->userLockedForFailedAttempt());
1013         userInfo.emplace("UserPasswordExpired",
1014                          user.get()->userPasswordExpired());
1015         userInfo.emplace("RemoteUser", false);
1016     }
1017     else
1018     {
1019         std::string ldapGroupName = getLdapGroupName(userName);
1020         if (ldapGroupName.empty())
1021         {
1022             log<level::ERR>("Unable to get group name",
1023                             entry("USER_NAME=%s", userName.c_str()));
1024             elog<InternalFailure>();
1025         }
1026 
1027         DbusUserObj objects = getPrivilegeMapperObject();
1028 
1029         std::string privilege;
1030         std::string groupName;
1031         std::string ldapConfigPath;
1032 
1033         try
1034         {
1035             for (const auto& obj : objects)
1036             {
1037                 for (const auto& interface : obj.second)
1038                 {
1039                     if ((interface.first ==
1040                          "xyz.openbmc_project.Object.Enable"))
1041                     {
1042                         for (const auto& property : interface.second)
1043                         {
1044                             auto value = std::get<bool>(property.second);
1045                             if ((property.first == "Enabled") &&
1046                                 (value == true))
1047                             {
1048                                 ldapConfigPath = obj.first;
1049                                 break;
1050                             }
1051                         }
1052                     }
1053                 }
1054                 if (!ldapConfigPath.empty())
1055                 {
1056                     break;
1057                 }
1058             }
1059 
1060             if (ldapConfigPath.empty())
1061             {
1062                 return userInfo;
1063             }
1064 
1065             for (const auto& obj : objects)
1066             {
1067                 for (const auto& interface : obj.second)
1068                 {
1069                     if ((interface.first ==
1070                          "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1071                         (obj.first.str.find(ldapConfigPath) !=
1072                          std::string::npos))
1073                     {
1074 
1075                         for (const auto& property : interface.second)
1076                         {
1077                             auto value = std::get<std::string>(property.second);
1078                             if (property.first == "GroupName")
1079                             {
1080                                 groupName = value;
1081                             }
1082                             else if (property.first == "Privilege")
1083                             {
1084                                 privilege = value;
1085                             }
1086                             if (groupName == ldapGroupName)
1087                             {
1088                                 userInfo["UserPrivilege"] = privilege;
1089                             }
1090                         }
1091                     }
1092                 }
1093             }
1094             auto priv = std::get<std::string>(userInfo["UserPrivilege"]);
1095 
1096             if (priv.empty())
1097             {
1098                 log<level::ERR>("LDAP group privilege mapping does not exist");
1099             }
1100         }
1101         catch (const std::bad_variant_access& e)
1102         {
1103             log<level::ERR>("Error while accessing variant",
1104                             entry("WHAT=%s", e.what()));
1105             elog<InternalFailure>();
1106         }
1107         userInfo.emplace("RemoteUser", true);
1108     }
1109 
1110     return userInfo;
1111 }
1112 
1113 void UserMgr::initUserObjects(void)
1114 {
1115     // All user management lock has to be based on /etc/shadow
1116     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1117     std::vector<std::string> userNameList;
1118     std::vector<std::string> sshGrpUsersList;
1119     UserSSHLists userSSHLists = getUserAndSshGrpList();
1120     userNameList = std::move(userSSHLists.first);
1121     sshGrpUsersList = std::move(userSSHLists.second);
1122 
1123     if (!userNameList.empty())
1124     {
1125         std::map<std::string, std::vector<std::string>> groupLists;
1126         for (auto& grp : groupsMgr)
1127         {
1128             if (grp == grpSsh)
1129             {
1130                 groupLists.emplace(grp, sshGrpUsersList);
1131             }
1132             else
1133             {
1134                 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1135                 groupLists.emplace(grp, grpUsersList);
1136             }
1137         }
1138         for (auto& grp : privMgr)
1139         {
1140             std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1141             groupLists.emplace(grp, grpUsersList);
1142         }
1143 
1144         for (auto& user : userNameList)
1145         {
1146             std::vector<std::string> userGroups;
1147             std::string userPriv;
1148             for (const auto& grp : groupLists)
1149             {
1150                 std::vector<std::string> tempGrp = grp.second;
1151                 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1152                     tempGrp.end())
1153                 {
1154                     if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1155                         privMgr.end())
1156                     {
1157                         userPriv = grp.first;
1158                     }
1159                     else
1160                     {
1161                         userGroups.emplace_back(grp.first);
1162                     }
1163                 }
1164             }
1165             // Add user objects to the Users path.
1166             sdbusplus::message::object_path tempObjPath(usersObjPath);
1167             tempObjPath /= user;
1168             std::string objPath(tempObjPath);
1169             std::sort(userGroups.begin(), userGroups.end());
1170             usersList.emplace(user,
1171                               std::move(std::make_unique<phosphor::user::Users>(
1172                                   bus, objPath.c_str(), userGroups, userPriv,
1173                                   isUserEnabled(user), *this)));
1174         }
1175     }
1176 }
1177 
1178 UserMgr::UserMgr(sdbusplus::bus::bus& bus, const char* path) :
1179     Ifaces(bus, path, true), bus(bus), path(path)
1180 {
1181     UserMgrIface::allPrivileges(privMgr);
1182     std::sort(groupsMgr.begin(), groupsMgr.end());
1183     UserMgrIface::allGroups(groupsMgr);
1184     std::string valueStr;
1185     auto value = minPasswdLength;
1186     unsigned long tmp = 0;
1187     if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1188         success)
1189     {
1190         AccountPolicyIface::minPasswordLength(minPasswdLength);
1191     }
1192     else
1193     {
1194         try
1195         {
1196             tmp = std::stoul(valueStr, nullptr);
1197             if (tmp > std::numeric_limits<decltype(value)>::max())
1198             {
1199                 throw std::out_of_range("Out of range");
1200             }
1201             value = static_cast<decltype(value)>(tmp);
1202         }
1203         catch (const std::exception& e)
1204         {
1205             log<level::ERR>("Exception for MinPasswordLength",
1206                             entry("WHAT=%s", e.what()));
1207             throw;
1208         }
1209         AccountPolicyIface::minPasswordLength(value);
1210     }
1211     valueStr.clear();
1212     if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1213         success)
1214     {
1215         AccountPolicyIface::rememberOldPasswordTimes(0);
1216     }
1217     else
1218     {
1219         value = 0;
1220         try
1221         {
1222             tmp = std::stoul(valueStr, nullptr);
1223             if (tmp > std::numeric_limits<decltype(value)>::max())
1224             {
1225                 throw std::out_of_range("Out of range");
1226             }
1227             value = static_cast<decltype(value)>(tmp);
1228         }
1229         catch (const std::exception& e)
1230         {
1231             log<level::ERR>("Exception for RememberOldPasswordTimes",
1232                             entry("WHAT=%s", e.what()));
1233             throw;
1234         }
1235         AccountPolicyIface::rememberOldPasswordTimes(value);
1236     }
1237     valueStr.clear();
1238     if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1239     {
1240         AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1241     }
1242     else
1243     {
1244         uint16_t value16 = 0;
1245         try
1246         {
1247             tmp = std::stoul(valueStr, nullptr);
1248             if (tmp > std::numeric_limits<decltype(value16)>::max())
1249             {
1250                 throw std::out_of_range("Out of range");
1251             }
1252             value16 = static_cast<decltype(value16)>(tmp);
1253         }
1254         catch (const std::exception& e)
1255         {
1256             log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1257                             entry("WHAT=%s", e.what()));
1258             throw;
1259         }
1260         AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1261     }
1262     valueStr.clear();
1263     if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1264     {
1265         AccountPolicyIface::accountUnlockTimeout(0);
1266     }
1267     else
1268     {
1269         uint32_t value32 = 0;
1270         try
1271         {
1272             tmp = std::stoul(valueStr, nullptr);
1273             if (tmp > std::numeric_limits<decltype(value32)>::max())
1274             {
1275                 throw std::out_of_range("Out of range");
1276             }
1277             value32 = static_cast<decltype(value32)>(tmp);
1278         }
1279         catch (const std::exception& e)
1280         {
1281             log<level::ERR>("Exception for AccountUnlockTimeout",
1282                             entry("WHAT=%s", e.what()));
1283             throw;
1284         }
1285         AccountPolicyIface::accountUnlockTimeout(value32);
1286     }
1287     initUserObjects();
1288 
1289     // emit the signal
1290     this->emit_object_added();
1291 }
1292 
1293 } // namespace user
1294 } // namespace phosphor
1295