xref: /openbmc/phosphor-user-manager/user_mgr.cpp (revision 78d850422ead4618b3c8fafafecf58c4b8c9f9ca)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 
17 #include "config.h"
18 
19 #include "user_mgr.hpp"
20 
21 #include "file.hpp"
22 #include "shadowlock.hpp"
23 #include "users.hpp"
24 
25 #include <grp.h>
26 #include <pwd.h>
27 #include <shadow.h>
28 #include <sys/types.h>
29 #include <sys/wait.h>
30 #include <time.h>
31 #include <unistd.h>
32 
33 #include <boost/algorithm/string/split.hpp>
34 #include <boost/process/child.hpp>
35 #include <boost/process/io.hpp>
36 #include <phosphor-logging/elog-errors.hpp>
37 #include <phosphor-logging/elog.hpp>
38 #include <phosphor-logging/log.hpp>
39 #include <xyz/openbmc_project/Common/error.hpp>
40 #include <xyz/openbmc_project/User/Common/error.hpp>
41 
42 #include <algorithm>
43 #include <ctime>
44 #include <fstream>
45 #include <numeric>
46 #include <regex>
47 
48 namespace phosphor
49 {
50 namespace user
51 {
52 
53 static constexpr const char* passwdFileName = "/etc/passwd";
54 static constexpr size_t ipmiMaxUsers = 15;
55 static constexpr size_t ipmiMaxUserNameLen = 16;
56 static constexpr size_t systemMaxUserNameLen = 30;
57 static constexpr size_t maxSystemUsers = 30;
58 static constexpr const char* grpSsh = "ssh";
59 static constexpr uint8_t minPasswdLength = 8;
60 static constexpr int success = 0;
61 static constexpr int failure = -1;
62 
63 // pam modules related
64 static constexpr const char* pamTally2 = "pam_tally2.so";
65 static constexpr const char* pamCrackLib = "pam_cracklib.so";
66 static constexpr const char* pamPWHistory = "pam_pwhistory.so";
67 static constexpr const char* minPasswdLenProp = "minlen";
68 static constexpr const char* remOldPasswdCount = "remember";
69 static constexpr const char* maxFailedAttempt = "deny";
70 static constexpr const char* unlockTimeout = "unlock_time";
71 static constexpr const char* pamPasswdConfigFile = "/etc/pam.d/common-password";
72 static constexpr const char* pamAuthConfigFile = "/etc/pam.d/common-auth";
73 
74 // Object Manager related
75 static constexpr const char* ldapMgrObjBasePath =
76     "/xyz/openbmc_project/user/ldap";
77 
78 // Object Mapper related
79 static constexpr const char* objMapperService =
80     "xyz.openbmc_project.ObjectMapper";
81 static constexpr const char* objMapperPath =
82     "/xyz/openbmc_project/object_mapper";
83 static constexpr const char* objMapperInterface =
84     "xyz.openbmc_project.ObjectMapper";
85 
86 using namespace phosphor::logging;
87 using InsufficientPermission =
88     sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
89 using InternalFailure =
90     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
91 using InvalidArgument =
92     sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
93 using UserNameExists =
94     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
95 using UserNameDoesNotExist =
96     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
97 using UserNameGroupFail =
98     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
99 using NoResource =
100     sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
101 
102 using Argument = xyz::openbmc_project::Common::InvalidArgument;
103 
104 template <typename... ArgTypes>
105 static std::vector<std::string> executeCmd(const char* path,
106                                            ArgTypes&&... tArgs)
107 {
108     std::vector<std::string> stdOutput;
109     boost::process::ipstream stdOutStream;
110     boost::process::child execProg(path, const_cast<char*>(tArgs)...,
111                                    boost::process::std_out > stdOutStream);
112     std::string stdOutLine;
113 
114     while (stdOutStream && std::getline(stdOutStream, stdOutLine) &&
115            !stdOutLine.empty())
116     {
117         stdOutput.emplace_back(stdOutLine);
118     }
119 
120     execProg.wait();
121 
122     int retCode = execProg.exit_code();
123     if (retCode)
124     {
125         log<level::ERR>("Command execution failed", entry("PATH=%s", path),
126                         entry("RETURN_CODE=%d", retCode));
127         elog<InternalFailure>();
128     }
129 
130     return stdOutput;
131 }
132 
133 static std::string getCSVFromVector(std::vector<std::string> vec)
134 {
135     switch (vec.size())
136     {
137         case 0:
138         {
139             return "";
140         }
141         break;
142 
143         case 1:
144         {
145             return std::string{vec[0]};
146         }
147         break;
148 
149         default:
150         {
151             return std::accumulate(
152                 std::next(vec.begin()), vec.end(), vec[0],
153                 [](std::string a, std::string b) { return a + ',' + b; });
154         }
155     }
156 }
157 
158 static bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
159 {
160     std::string::size_type delStrPos = csvStr.find(delStr);
161     if (delStrPos != std::string::npos)
162     {
163         // need to also delete the comma char
164         if (delStrPos == 0)
165         {
166             csvStr.erase(delStrPos, delStr.size() + 1);
167         }
168         else
169         {
170             csvStr.erase(delStrPos - 1, delStr.size() + 1);
171         }
172         return true;
173     }
174     return false;
175 }
176 
177 bool UserMgr::isUserExist(const std::string& userName)
178 {
179     if (userName.empty())
180     {
181         log<level::ERR>("User name is empty");
182         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
183                               Argument::ARGUMENT_VALUE("Null"));
184     }
185     if (usersList.find(userName) == usersList.end())
186     {
187         return false;
188     }
189     return true;
190 }
191 
192 void UserMgr::throwForUserDoesNotExist(const std::string& userName)
193 {
194     if (isUserExist(userName) == false)
195     {
196         log<level::ERR>("User does not exist",
197                         entry("USER_NAME=%s", userName.c_str()));
198         elog<UserNameDoesNotExist>();
199     }
200 }
201 
202 void UserMgr::throwForUserExists(const std::string& userName)
203 {
204     if (isUserExist(userName) == true)
205     {
206         log<level::ERR>("User already exists",
207                         entry("USER_NAME=%s", userName.c_str()));
208         elog<UserNameExists>();
209     }
210 }
211 
212 void UserMgr::throwForUserNameConstraints(
213     const std::string& userName, const std::vector<std::string>& groupNames)
214 {
215     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
216         groupNames.end())
217     {
218         if (userName.length() > ipmiMaxUserNameLen)
219         {
220             log<level::ERR>("IPMI user name length limitation",
221                             entry("SIZE=%d", userName.length()));
222             elog<UserNameGroupFail>(
223                 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
224                     "IPMI length"));
225         }
226     }
227     if (userName.length() > systemMaxUserNameLen)
228     {
229         log<level::ERR>("User name length limitation",
230                         entry("SIZE=%d", userName.length()));
231         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
232                               Argument::ARGUMENT_VALUE("Invalid length"));
233     }
234     if (!std::regex_match(userName.c_str(),
235                           std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
236     {
237         log<level::ERR>("Invalid user name",
238                         entry("USER_NAME=%s", userName.c_str()));
239         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
240                               Argument::ARGUMENT_VALUE("Invalid data"));
241     }
242 }
243 
244 void UserMgr::throwForMaxGrpUserCount(
245     const std::vector<std::string>& groupNames)
246 {
247     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
248         groupNames.end())
249     {
250         if (getIpmiUsersCount() >= ipmiMaxUsers)
251         {
252             log<level::ERR>("IPMI user limit reached");
253             elog<NoResource>(
254                 xyz::openbmc_project::User::Common::NoResource::REASON(
255                     "ipmi user count reached"));
256         }
257     }
258     else
259     {
260         if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
261                                         (maxSystemUsers - ipmiMaxUsers))
262         {
263             log<level::ERR>("Non-ipmi User limit reached");
264             elog<NoResource>(
265                 xyz::openbmc_project::User::Common::NoResource::REASON(
266                     "Non-ipmi user count reached"));
267         }
268     }
269     return;
270 }
271 
272 void UserMgr::throwForInvalidPrivilege(const std::string& priv)
273 {
274     if (!priv.empty() &&
275         (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
276     {
277         log<level::ERR>("Invalid privilege");
278         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
279                               Argument::ARGUMENT_VALUE(priv.c_str()));
280     }
281 }
282 
283 void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
284 {
285     for (auto& group : groupNames)
286     {
287         if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
288             groupsMgr.end())
289         {
290             log<level::ERR>("Invalid Group Name listed");
291             elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
292                                   Argument::ARGUMENT_VALUE(group.c_str()));
293         }
294     }
295 }
296 
297 void UserMgr::createUser(std::string userName,
298                          std::vector<std::string> groupNames, std::string priv,
299                          bool enabled)
300 {
301     throwForInvalidPrivilege(priv);
302     throwForInvalidGroups(groupNames);
303     // All user management lock has to be based on /etc/shadow
304     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
305     throwForUserExists(userName);
306     throwForUserNameConstraints(userName, groupNames);
307     throwForMaxGrpUserCount(groupNames);
308 
309     std::string groups = getCSVFromVector(groupNames);
310     bool sshRequested = removeStringFromCSV(groups, grpSsh);
311 
312     // treat privilege as a group - This is to avoid using different file to
313     // store the same.
314     if (!priv.empty())
315     {
316         if (groups.size() != 0)
317         {
318             groups += ",";
319         }
320         groups += priv;
321     }
322     try
323     {
324         // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
325         // 1970-01-01, that's an implementation-defined behavior
326         executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
327                    "-m", "-N", "-s",
328                    (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
329                    (enabled ? "" : "1970-01-01"));
330     }
331     catch (const InternalFailure& e)
332     {
333         log<level::ERR>("Unable to create new user");
334         elog<InternalFailure>();
335     }
336 
337     // Add the users object before sending out the signal
338     sdbusplus::message::object_path tempObjPath(usersObjPath);
339     tempObjPath /= userName;
340     std::string userObj(tempObjPath);
341     std::sort(groupNames.begin(), groupNames.end());
342     usersList.emplace(
343         userName, std::make_unique<phosphor::user::Users>(
344                       bus, userObj.c_str(), groupNames, priv, enabled, *this));
345 
346     log<level::INFO>("User created successfully",
347                      entry("USER_NAME=%s", userName.c_str()));
348     return;
349 }
350 
351 void UserMgr::deleteUser(std::string userName)
352 {
353     // All user management lock has to be based on /etc/shadow
354     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
355     throwForUserDoesNotExist(userName);
356     try
357     {
358         executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
359     }
360     catch (const InternalFailure& e)
361     {
362         log<level::ERR>("User delete failed",
363                         entry("USER_NAME=%s", userName.c_str()));
364         elog<InternalFailure>();
365     }
366 
367     usersList.erase(userName);
368 
369     log<level::INFO>("User deleted successfully",
370                      entry("USER_NAME=%s", userName.c_str()));
371     return;
372 }
373 
374 void UserMgr::renameUser(std::string userName, std::string newUserName)
375 {
376     // All user management lock has to be based on /etc/shadow
377     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
378     throwForUserDoesNotExist(userName);
379     throwForUserExists(newUserName);
380     throwForUserNameConstraints(newUserName,
381                                 usersList[userName].get()->userGroups());
382     try
383     {
384         std::string newHomeDir = "/home/" + newUserName;
385         executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
386                    userName.c_str(), "-d", newHomeDir.c_str(), "-m");
387     }
388     catch (const InternalFailure& e)
389     {
390         log<level::ERR>("User rename failed",
391                         entry("USER_NAME=%s", userName.c_str()));
392         elog<InternalFailure>();
393     }
394     const auto& user = usersList[userName];
395     std::string priv = user.get()->userPrivilege();
396     std::vector<std::string> groupNames = user.get()->userGroups();
397     bool enabled = user.get()->userEnabled();
398     sdbusplus::message::object_path tempObjPath(usersObjPath);
399     tempObjPath /= newUserName;
400     std::string newUserObj(tempObjPath);
401     // Special group 'ipmi' needs a way to identify user renamed, in order to
402     // update encrypted password. It can't rely only on InterfacesRemoved &
403     // InterfacesAdded. So first send out userRenamed signal.
404     this->userRenamed(userName, newUserName);
405     usersList.erase(userName);
406     usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
407                                        bus, newUserObj.c_str(), groupNames,
408                                        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 failure date, [3] - latest failure time
685  * [4] - failure app
686  **/
687 
688 static constexpr size_t t2FailCntIdx = 1;
689 static constexpr size_t t2FailDateIdx = 2;
690 static constexpr size_t t2FailTimeIdx = 3;
691 static constexpr size_t t2OutputIndex = 1;
692 
693 bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
694 {
695     // All user management lock has to be based on /etc/shadow
696     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
697     if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
698     {
699         return false;
700     }
701 
702     std::vector<std::string> output;
703     try
704     {
705         output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
706     }
707     catch (const InternalFailure& e)
708     {
709         log<level::ERR>("Unable to read login failure counter");
710         elog<InternalFailure>();
711     }
712 
713     std::vector<std::string> splitWords;
714     boost::algorithm::split(splitWords, output[t2OutputIndex],
715                             boost::algorithm::is_any_of("\t "),
716                             boost::token_compress_on);
717 
718     uint16_t failAttempts = 0;
719     try
720     {
721         unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
722         if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
723         {
724             throw std::out_of_range("Out of range");
725         }
726         failAttempts = static_cast<decltype(failAttempts)>(tmp);
727     }
728     catch (const std::exception& e)
729     {
730         log<level::ERR>("Exception for userLockedForFailedAttempt",
731                         entry("WHAT=%s", e.what()));
732         elog<InternalFailure>();
733     }
734 
735     if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
736     {
737         return false;
738     }
739 
740     // When failedAttempts is not 0, Latest failure date/time should be
741     // available
742     if (splitWords.size() < 4)
743     {
744         log<level::ERR>("Unable to read latest failure date/time");
745         elog<InternalFailure>();
746     }
747 
748     const std::string failDateTime =
749         splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
750 
751     // NOTE: Cannot use std::get_time() here as the implementation of %y in
752     // libstdc++ does not match POSIX strptime() before gcc 12.1.0
753     // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
754     std::tm tmStruct = {};
755     if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
756     {
757         log<level::ERR>("Failed to parse latest failure date/time");
758         elog<InternalFailure>();
759     }
760 
761     time_t failTimestamp = std::mktime(&tmStruct);
762     if (failTimestamp +
763             static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
764         std::time(NULL))
765     {
766         return false;
767     }
768 
769     return true;
770 }
771 
772 bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
773                                          const bool& value)
774 {
775     // All user management lock has to be based on /etc/shadow
776     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
777     if (value == true)
778     {
779         return userLockedForFailedAttempt(userName);
780     }
781 
782     try
783     {
784         executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
785     }
786     catch (const InternalFailure& e)
787     {
788         log<level::ERR>("Unable to reset login failure counter");
789         elog<InternalFailure>();
790     }
791 
792     return userLockedForFailedAttempt(userName);
793 }
794 
795 bool UserMgr::userPasswordExpired(const std::string& userName)
796 {
797     // All user management lock has to be based on /etc/shadow
798     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
799 
800     struct spwd spwd
801     {};
802     struct spwd* spwdPtr = nullptr;
803     auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
804     if (buflen < -1)
805     {
806         // Use a default size if there is no hard limit suggested by sysconf()
807         buflen = 1024;
808     }
809     std::vector<char> buffer(buflen);
810     auto status =
811         getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
812     // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
813     // If no matching password record was found, these functions return 0
814     // and store NULL in *spwdPtr
815     if ((status == 0) && (&spwd == spwdPtr))
816     {
817         // Determine password validity per "chage" docs, where:
818         //   spwd.sp_lstchg == 0 means password is expired, and
819         //   spwd.sp_max == -1 means the password does not expire.
820         constexpr long secondsPerDay = 60 * 60 * 24;
821         long today = static_cast<long>(time(NULL)) / secondsPerDay;
822         if ((spwd.sp_lstchg == 0) ||
823             ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
824         {
825             return true;
826         }
827     }
828     else
829     {
830         // User entry is missing in /etc/shadow, indicating no SHA password.
831         // Treat this as new user without password entry in /etc/shadow
832         // TODO: Add property to indicate user password was not set yet
833         // https://github.com/openbmc/phosphor-user-manager/issues/8
834         return false;
835     }
836 
837     return false;
838 }
839 
840 UserSSHLists UserMgr::getUserAndSshGrpList()
841 {
842     // All user management lock has to be based on /etc/shadow
843     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
844 
845     std::vector<std::string> userList;
846     std::vector<std::string> sshUsersList;
847     struct passwd pw, *pwp = nullptr;
848     std::array<char, 1024> buffer{};
849 
850     phosphor::user::File passwd(passwdFileName, "r");
851     if ((passwd)() == NULL)
852     {
853         log<level::ERR>("Error opening the passwd file");
854         elog<InternalFailure>();
855     }
856 
857     while (true)
858     {
859         auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
860                              &pwp);
861         if ((r != 0) || (pwp == NULL))
862         {
863             // Any error, break the loop.
864             break;
865         }
866 #ifdef ENABLE_ROOT_USER_MGMT
867         // Add all users whose UID >= 1000 and < 65534
868         // and special UID 0.
869         if ((pwp->pw_uid == 0) ||
870             ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
871 #else
872         // Add all users whose UID >=1000 and < 65534
873         if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
874 #endif
875         {
876             std::string userName(pwp->pw_name);
877             userList.emplace_back(userName);
878 
879             // ssh doesn't have separate group. Check login shell entry to
880             // get all users list which are member of ssh group.
881             std::string loginShell(pwp->pw_shell);
882             if (loginShell == "/bin/sh")
883             {
884                 sshUsersList.emplace_back(userName);
885             }
886         }
887     }
888     endpwent();
889     return std::make_pair(std::move(userList), std::move(sshUsersList));
890 }
891 
892 size_t UserMgr::getIpmiUsersCount()
893 {
894     std::vector<std::string> userList = getUsersInGroup("ipmi");
895     return userList.size();
896 }
897 
898 bool UserMgr::isUserEnabled(const std::string& userName)
899 {
900     // All user management lock has to be based on /etc/shadow
901     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
902     std::array<char, 4096> buffer{};
903     struct spwd spwd;
904     struct spwd* resultPtr = nullptr;
905     int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
906                             buffer.max_size(), &resultPtr);
907     if (!status && (&spwd == resultPtr))
908     {
909         if (resultPtr->sp_expire >= 0)
910         {
911             return false; // user locked out
912         }
913         return true;
914     }
915     return false; // assume user is disabled for any error.
916 }
917 
918 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
919 {
920     std::vector<std::string> usersInGroup;
921     // Should be more than enough to get the pwd structure.
922     std::array<char, 4096> buffer{};
923     struct group grp;
924     struct group* resultPtr = nullptr;
925 
926     int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
927                             buffer.max_size(), &resultPtr);
928 
929     if (!status && (&grp == resultPtr))
930     {
931         for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
932         {
933             usersInGroup.emplace_back(*(grp.gr_mem));
934         }
935     }
936     else
937     {
938         log<level::ERR>("Group not found",
939                         entry("GROUP=%s", groupName.c_str()));
940         // Don't throw error, just return empty userList - fallback
941     }
942     return usersInGroup;
943 }
944 
945 DbusUserObj UserMgr::getPrivilegeMapperObject(void)
946 {
947     DbusUserObj objects;
948     try
949     {
950         std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
951         std::string interface = "xyz.openbmc_project.User.Ldap.Config";
952 
953         auto ldapMgmtService =
954             getServiceName(std::move(basePath), std::move(interface));
955         auto method = bus.new_method_call(
956             ldapMgmtService.c_str(), ldapMgrObjBasePath,
957             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
958 
959         auto reply = bus.call(method);
960         reply.read(objects);
961     }
962     catch (const InternalFailure& e)
963     {
964         log<level::ERR>("Unable to get the User Service",
965                         entry("WHAT=%s", e.what()));
966         throw;
967     }
968     catch (const sdbusplus::exception_t& e)
969     {
970         log<level::ERR>(
971             "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
972             entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
973         throw;
974     }
975     return objects;
976 }
977 
978 std::string UserMgr::getLdapGroupName(const std::string& userName)
979 {
980     struct passwd pwd
981     {};
982     struct passwd* pwdPtr = nullptr;
983     auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
984     if (buflen < -1)
985     {
986         // Use a default size if there is no hard limit suggested by sysconf()
987         buflen = 1024;
988     }
989     std::vector<char> buffer(buflen);
990     gid_t gid = 0;
991 
992     auto status =
993         getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
994     // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
995     // If no matching password record was found, these functions return 0
996     // and store NULL in *pwdPtr
997     if (!status && (&pwd == pwdPtr))
998     {
999         gid = pwd.pw_gid;
1000     }
1001     else
1002     {
1003         log<level::ERR>("User does not exist",
1004                         entry("USER_NAME=%s", userName.c_str()));
1005         elog<UserNameDoesNotExist>();
1006     }
1007 
1008     struct group* groups = nullptr;
1009     std::string ldapGroupName;
1010 
1011     while ((groups = getgrent()) != NULL)
1012     {
1013         if (groups->gr_gid == gid)
1014         {
1015             ldapGroupName = groups->gr_name;
1016             break;
1017         }
1018     }
1019     // Call endgrent() to close the group database.
1020     endgrent();
1021 
1022     return ldapGroupName;
1023 }
1024 
1025 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
1026 {
1027     auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1028                                           objMapperInterface, "GetObject");
1029 
1030     mapperCall.append(std::move(path));
1031     mapperCall.append(std::vector<std::string>({std::move(intf)}));
1032 
1033     auto mapperResponseMsg = bus.call(mapperCall);
1034 
1035     if (mapperResponseMsg.is_method_error())
1036     {
1037         log<level::ERR>("Error in mapper call");
1038         elog<InternalFailure>();
1039     }
1040 
1041     std::map<std::string, std::vector<std::string>> mapperResponse;
1042     mapperResponseMsg.read(mapperResponse);
1043 
1044     if (mapperResponse.begin() == mapperResponse.end())
1045     {
1046         log<level::ERR>("Invalid response from mapper");
1047         elog<InternalFailure>();
1048     }
1049 
1050     return mapperResponse.begin()->first;
1051 }
1052 
1053 UserInfoMap UserMgr::getUserInfo(std::string userName)
1054 {
1055     UserInfoMap userInfo;
1056     // Check whether the given user is local user or not.
1057     if (isUserExist(userName) == true)
1058     {
1059         const auto& user = usersList[userName];
1060         userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1061         userInfo.emplace("UserGroups", user.get()->userGroups());
1062         userInfo.emplace("UserEnabled", user.get()->userEnabled());
1063         userInfo.emplace("UserLockedForFailedAttempt",
1064                          user.get()->userLockedForFailedAttempt());
1065         userInfo.emplace("UserPasswordExpired",
1066                          user.get()->userPasswordExpired());
1067         userInfo.emplace("RemoteUser", false);
1068     }
1069     else
1070     {
1071         std::string ldapGroupName = getLdapGroupName(userName);
1072         if (ldapGroupName.empty())
1073         {
1074             log<level::ERR>("Unable to get group name",
1075                             entry("USER_NAME=%s", userName.c_str()));
1076             elog<InternalFailure>();
1077         }
1078 
1079         DbusUserObj objects = getPrivilegeMapperObject();
1080 
1081         std::string ldapConfigPath;
1082         std::string userPrivilege;
1083 
1084         try
1085         {
1086             for (const auto& obj : objects)
1087             {
1088                 for (const auto& interface : obj.second)
1089                 {
1090                     if ((interface.first ==
1091                          "xyz.openbmc_project.Object.Enable"))
1092                     {
1093                         for (const auto& property : interface.second)
1094                         {
1095                             auto value = std::get<bool>(property.second);
1096                             if ((property.first == "Enabled") &&
1097                                 (value == true))
1098                             {
1099                                 ldapConfigPath = obj.first;
1100                                 break;
1101                             }
1102                         }
1103                     }
1104                 }
1105                 if (!ldapConfigPath.empty())
1106                 {
1107                     break;
1108                 }
1109             }
1110 
1111             if (ldapConfigPath.empty())
1112             {
1113                 return userInfo;
1114             }
1115 
1116             for (const auto& obj : objects)
1117             {
1118                 for (const auto& interface : obj.second)
1119                 {
1120                     if ((interface.first ==
1121                          "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1122                         (obj.first.str.find(ldapConfigPath) !=
1123                          std::string::npos))
1124                     {
1125                         std::string privilege;
1126                         std::string groupName;
1127 
1128                         for (const auto& property : interface.second)
1129                         {
1130                             auto value = std::get<std::string>(property.second);
1131                             if (property.first == "GroupName")
1132                             {
1133                                 groupName = value;
1134                             }
1135                             else if (property.first == "Privilege")
1136                             {
1137                                 privilege = value;
1138                             }
1139                         }
1140                         if (groupName == ldapGroupName)
1141                         {
1142                             userPrivilege = privilege;
1143                             break;
1144                         }
1145                     }
1146                 }
1147                 if (!userPrivilege.empty())
1148                 {
1149                     break;
1150                 }
1151             }
1152 
1153             if (userPrivilege.empty())
1154             {
1155                 log<level::ERR>("LDAP group privilege mapping does not exist");
1156             }
1157             userInfo.emplace("UserPrivilege", userPrivilege);
1158         }
1159         catch (const std::bad_variant_access& e)
1160         {
1161             log<level::ERR>("Error while accessing variant",
1162                             entry("WHAT=%s", e.what()));
1163             elog<InternalFailure>();
1164         }
1165         userInfo.emplace("RemoteUser", true);
1166     }
1167 
1168     return userInfo;
1169 }
1170 
1171 void UserMgr::initUserObjects(void)
1172 {
1173     // All user management lock has to be based on /etc/shadow
1174     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1175     std::vector<std::string> userNameList;
1176     std::vector<std::string> sshGrpUsersList;
1177     UserSSHLists userSSHLists = getUserAndSshGrpList();
1178     userNameList = std::move(userSSHLists.first);
1179     sshGrpUsersList = std::move(userSSHLists.second);
1180 
1181     if (!userNameList.empty())
1182     {
1183         std::map<std::string, std::vector<std::string>> groupLists;
1184         for (auto& grp : groupsMgr)
1185         {
1186             if (grp == grpSsh)
1187             {
1188                 groupLists.emplace(grp, sshGrpUsersList);
1189             }
1190             else
1191             {
1192                 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1193                 groupLists.emplace(grp, grpUsersList);
1194             }
1195         }
1196         for (auto& grp : privMgr)
1197         {
1198             std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1199             groupLists.emplace(grp, grpUsersList);
1200         }
1201 
1202         for (auto& user : userNameList)
1203         {
1204             std::vector<std::string> userGroups;
1205             std::string userPriv;
1206             for (const auto& grp : groupLists)
1207             {
1208                 std::vector<std::string> tempGrp = grp.second;
1209                 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1210                     tempGrp.end())
1211                 {
1212                     if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1213                         privMgr.end())
1214                     {
1215                         userPriv = grp.first;
1216                     }
1217                     else
1218                     {
1219                         userGroups.emplace_back(grp.first);
1220                     }
1221                 }
1222             }
1223             // Add user objects to the Users path.
1224             sdbusplus::message::object_path tempObjPath(usersObjPath);
1225             tempObjPath /= user;
1226             std::string objPath(tempObjPath);
1227             std::sort(userGroups.begin(), userGroups.end());
1228             usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1229                                         bus, objPath.c_str(), userGroups,
1230                                         userPriv, isUserEnabled(user), *this));
1231         }
1232     }
1233 }
1234 
1235 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1236     Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path)
1237 {
1238     UserMgrIface::allPrivileges(privMgr);
1239     std::sort(groupsMgr.begin(), groupsMgr.end());
1240     UserMgrIface::allGroups(groupsMgr);
1241     std::string valueStr;
1242     auto value = minPasswdLength;
1243     unsigned long tmp = 0;
1244     if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1245         success)
1246     {
1247         AccountPolicyIface::minPasswordLength(minPasswdLength);
1248     }
1249     else
1250     {
1251         try
1252         {
1253             tmp = std::stoul(valueStr, nullptr);
1254             if (tmp > std::numeric_limits<decltype(value)>::max())
1255             {
1256                 throw std::out_of_range("Out of range");
1257             }
1258             value = static_cast<decltype(value)>(tmp);
1259         }
1260         catch (const std::exception& e)
1261         {
1262             log<level::ERR>("Exception for MinPasswordLength",
1263                             entry("WHAT=%s", e.what()));
1264             throw;
1265         }
1266         AccountPolicyIface::minPasswordLength(value);
1267     }
1268     valueStr.clear();
1269     if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1270         success)
1271     {
1272         AccountPolicyIface::rememberOldPasswordTimes(0);
1273     }
1274     else
1275     {
1276         value = 0;
1277         try
1278         {
1279             tmp = std::stoul(valueStr, nullptr);
1280             if (tmp > std::numeric_limits<decltype(value)>::max())
1281             {
1282                 throw std::out_of_range("Out of range");
1283             }
1284             value = static_cast<decltype(value)>(tmp);
1285         }
1286         catch (const std::exception& e)
1287         {
1288             log<level::ERR>("Exception for RememberOldPasswordTimes",
1289                             entry("WHAT=%s", e.what()));
1290             throw;
1291         }
1292         AccountPolicyIface::rememberOldPasswordTimes(value);
1293     }
1294     valueStr.clear();
1295     if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1296     {
1297         AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1298     }
1299     else
1300     {
1301         uint16_t value16 = 0;
1302         try
1303         {
1304             tmp = std::stoul(valueStr, nullptr);
1305             if (tmp > std::numeric_limits<decltype(value16)>::max())
1306             {
1307                 throw std::out_of_range("Out of range");
1308             }
1309             value16 = static_cast<decltype(value16)>(tmp);
1310         }
1311         catch (const std::exception& e)
1312         {
1313             log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1314                             entry("WHAT=%s", e.what()));
1315             throw;
1316         }
1317         AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1318     }
1319     valueStr.clear();
1320     if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1321     {
1322         AccountPolicyIface::accountUnlockTimeout(0);
1323     }
1324     else
1325     {
1326         uint32_t value32 = 0;
1327         try
1328         {
1329             tmp = std::stoul(valueStr, nullptr);
1330             if (tmp > std::numeric_limits<decltype(value32)>::max())
1331             {
1332                 throw std::out_of_range("Out of range");
1333             }
1334             value32 = static_cast<decltype(value32)>(tmp);
1335         }
1336         catch (const std::exception& e)
1337         {
1338             log<level::ERR>("Exception for AccountUnlockTimeout",
1339                             entry("WHAT=%s", e.what()));
1340             throw;
1341         }
1342         AccountPolicyIface::accountUnlockTimeout(value32);
1343     }
1344     initUserObjects();
1345 
1346     // emit the signal
1347     this->emit_object_added();
1348 }
1349 
1350 } // namespace user
1351 } // namespace phosphor
1352