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