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::getLdapGroupName(const std::string& userName)
939 {
940     struct passwd pwd
941     {};
942     struct passwd* pwdPtr = nullptr;
943     auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
944     if (buflen < -1)
945     {
946         // Use a default size if there is no hard limit suggested by sysconf()
947         buflen = 1024;
948     }
949     std::vector<char> buffer(buflen);
950     gid_t gid = 0;
951 
952     auto status =
953         getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
954     // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
955     // If no matching password record was found, these functions return 0
956     // and store NULL in *pwdPtr
957     if (!status && (&pwd == pwdPtr))
958     {
959         gid = pwd.pw_gid;
960     }
961     else
962     {
963         log<level::ERR>("User does not exist",
964                         entry("USER_NAME=%s", userName.c_str()));
965         elog<UserNameDoesNotExist>();
966     }
967 
968     struct group* groups = nullptr;
969     std::string ldapGroupName;
970 
971     while ((groups = getgrent()) != NULL)
972     {
973         if (groups->gr_gid == gid)
974         {
975             ldapGroupName = groups->gr_name;
976             break;
977         }
978     }
979     // Call endgrent() to close the group database.
980     endgrent();
981 
982     return ldapGroupName;
983 }
984 
985 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
986 {
987     auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
988                                           objMapperInterface, "GetObject");
989 
990     mapperCall.append(std::move(path));
991     mapperCall.append(std::vector<std::string>({std::move(intf)}));
992 
993     auto mapperResponseMsg = bus.call(mapperCall);
994 
995     if (mapperResponseMsg.is_method_error())
996     {
997         log<level::ERR>("Error in mapper call");
998         elog<InternalFailure>();
999     }
1000 
1001     std::map<std::string, std::vector<std::string>> mapperResponse;
1002     mapperResponseMsg.read(mapperResponse);
1003 
1004     if (mapperResponse.begin() == mapperResponse.end())
1005     {
1006         log<level::ERR>("Invalid response from mapper");
1007         elog<InternalFailure>();
1008     }
1009 
1010     return mapperResponse.begin()->first;
1011 }
1012 
1013 UserInfoMap UserMgr::getUserInfo(std::string userName)
1014 {
1015     UserInfoMap userInfo;
1016     // Check whether the given user is local user or not.
1017     if (isUserExist(userName))
1018     {
1019         const auto& user = usersList[userName];
1020         userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1021         userInfo.emplace("UserGroups", user.get()->userGroups());
1022         userInfo.emplace("UserEnabled", user.get()->userEnabled());
1023         userInfo.emplace("UserLockedForFailedAttempt",
1024                          user.get()->userLockedForFailedAttempt());
1025         userInfo.emplace("UserPasswordExpired",
1026                          user.get()->userPasswordExpired());
1027         userInfo.emplace("RemoteUser", false);
1028     }
1029     else
1030     {
1031         std::string ldapGroupName = getLdapGroupName(userName);
1032         if (ldapGroupName.empty())
1033         {
1034             log<level::ERR>("Unable to get group name",
1035                             entry("USER_NAME=%s", userName.c_str()));
1036             elog<InternalFailure>();
1037         }
1038 
1039         DbusUserObj objects = getPrivilegeMapperObject();
1040 
1041         std::string ldapConfigPath;
1042         std::string userPrivilege;
1043 
1044         try
1045         {
1046             for (const auto& obj : objects)
1047             {
1048                 for (const auto& interface : obj.second)
1049                 {
1050                     if ((interface.first ==
1051                          "xyz.openbmc_project.Object.Enable"))
1052                     {
1053                         for (const auto& property : interface.second)
1054                         {
1055                             auto value = std::get<bool>(property.second);
1056                             if ((property.first == "Enabled") &&
1057                                 (value == true))
1058                             {
1059                                 ldapConfigPath = obj.first;
1060                                 break;
1061                             }
1062                         }
1063                     }
1064                 }
1065                 if (!ldapConfigPath.empty())
1066                 {
1067                     break;
1068                 }
1069             }
1070 
1071             if (ldapConfigPath.empty())
1072             {
1073                 return userInfo;
1074             }
1075 
1076             for (const auto& obj : objects)
1077             {
1078                 for (const auto& interface : obj.second)
1079                 {
1080                     if ((interface.first ==
1081                          "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1082                         (obj.first.str.find(ldapConfigPath) !=
1083                          std::string::npos))
1084                     {
1085                         std::string privilege;
1086                         std::string groupName;
1087 
1088                         for (const auto& property : interface.second)
1089                         {
1090                             auto value = std::get<std::string>(property.second);
1091                             if (property.first == "GroupName")
1092                             {
1093                                 groupName = value;
1094                             }
1095                             else if (property.first == "Privilege")
1096                             {
1097                                 privilege = value;
1098                             }
1099                         }
1100                         if (groupName == ldapGroupName)
1101                         {
1102                             userPrivilege = privilege;
1103                             break;
1104                         }
1105                     }
1106                 }
1107                 if (!userPrivilege.empty())
1108                 {
1109                     break;
1110                 }
1111             }
1112 
1113             if (userPrivilege.empty())
1114             {
1115                 log<level::ERR>("LDAP group privilege mapping does not exist");
1116             }
1117             userInfo.emplace("UserPrivilege", userPrivilege);
1118         }
1119         catch (const std::bad_variant_access& e)
1120         {
1121             log<level::ERR>("Error while accessing variant",
1122                             entry("WHAT=%s", e.what()));
1123             elog<InternalFailure>();
1124         }
1125         userInfo.emplace("RemoteUser", true);
1126     }
1127 
1128     return userInfo;
1129 }
1130 
1131 void UserMgr::initializeAccountPolicy()
1132 {
1133     std::string valueStr;
1134     auto value = minPasswdLength;
1135     unsigned long tmp = 0;
1136     if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1137         success)
1138     {
1139         AccountPolicyIface::minPasswordLength(minPasswdLength);
1140     }
1141     else
1142     {
1143         try
1144         {
1145             tmp = std::stoul(valueStr, nullptr);
1146             if (tmp > std::numeric_limits<decltype(value)>::max())
1147             {
1148                 throw std::out_of_range("Out of range");
1149             }
1150             value = static_cast<decltype(value)>(tmp);
1151         }
1152         catch (const std::exception& e)
1153         {
1154             log<level::ERR>("Exception for MinPasswordLength",
1155                             entry("WHAT=%s", e.what()));
1156             throw;
1157         }
1158         AccountPolicyIface::minPasswordLength(value);
1159     }
1160     valueStr.clear();
1161     if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1162         success)
1163     {
1164         AccountPolicyIface::rememberOldPasswordTimes(0);
1165     }
1166     else
1167     {
1168         value = 0;
1169         try
1170         {
1171             tmp = std::stoul(valueStr, nullptr);
1172             if (tmp > std::numeric_limits<decltype(value)>::max())
1173             {
1174                 throw std::out_of_range("Out of range");
1175             }
1176             value = static_cast<decltype(value)>(tmp);
1177         }
1178         catch (const std::exception& e)
1179         {
1180             log<level::ERR>("Exception for RememberOldPasswordTimes",
1181                             entry("WHAT=%s", e.what()));
1182             throw;
1183         }
1184         AccountPolicyIface::rememberOldPasswordTimes(value);
1185     }
1186     valueStr.clear();
1187     if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1188     {
1189         AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1190     }
1191     else
1192     {
1193         uint16_t value16 = 0;
1194         try
1195         {
1196             tmp = std::stoul(valueStr, nullptr);
1197             if (tmp > std::numeric_limits<decltype(value16)>::max())
1198             {
1199                 throw std::out_of_range("Out of range");
1200             }
1201             value16 = static_cast<decltype(value16)>(tmp);
1202         }
1203         catch (const std::exception& e)
1204         {
1205             log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1206                             entry("WHAT=%s", e.what()));
1207             throw;
1208         }
1209         AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1210     }
1211     valueStr.clear();
1212     if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1213     {
1214         AccountPolicyIface::accountUnlockTimeout(0);
1215     }
1216     else
1217     {
1218         uint32_t value32 = 0;
1219         try
1220         {
1221             tmp = std::stoul(valueStr, nullptr);
1222             if (tmp > std::numeric_limits<decltype(value32)>::max())
1223             {
1224                 throw std::out_of_range("Out of range");
1225             }
1226             value32 = static_cast<decltype(value32)>(tmp);
1227         }
1228         catch (const std::exception& e)
1229         {
1230             log<level::ERR>("Exception for AccountUnlockTimeout",
1231                             entry("WHAT=%s", e.what()));
1232             throw;
1233         }
1234         AccountPolicyIface::accountUnlockTimeout(value32);
1235     }
1236 }
1237 
1238 void UserMgr::initUserObjects(void)
1239 {
1240     // All user management lock has to be based on /etc/shadow
1241     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1242     std::vector<std::string> userNameList;
1243     std::vector<std::string> sshGrpUsersList;
1244     UserSSHLists userSSHLists = getUserAndSshGrpList();
1245     userNameList = std::move(userSSHLists.first);
1246     sshGrpUsersList = std::move(userSSHLists.second);
1247 
1248     if (!userNameList.empty())
1249     {
1250         std::map<std::string, std::vector<std::string>> groupLists;
1251         for (auto& grp : groupsMgr)
1252         {
1253             if (grp == grpSsh)
1254             {
1255                 groupLists.emplace(grp, sshGrpUsersList);
1256             }
1257             else
1258             {
1259                 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1260                 groupLists.emplace(grp, grpUsersList);
1261             }
1262         }
1263         for (auto& grp : privMgr)
1264         {
1265             std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1266             groupLists.emplace(grp, grpUsersList);
1267         }
1268 
1269         for (auto& user : userNameList)
1270         {
1271             std::vector<std::string> userGroups;
1272             std::string userPriv;
1273             for (const auto& grp : groupLists)
1274             {
1275                 std::vector<std::string> tempGrp = grp.second;
1276                 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1277                     tempGrp.end())
1278                 {
1279                     if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1280                         privMgr.end())
1281                     {
1282                         userPriv = grp.first;
1283                     }
1284                     else
1285                     {
1286                         userGroups.emplace_back(grp.first);
1287                     }
1288                 }
1289             }
1290             // Add user objects to the Users path.
1291             sdbusplus::message::object_path tempObjPath(usersObjPath);
1292             tempObjPath /= user;
1293             std::string objPath(tempObjPath);
1294             std::sort(userGroups.begin(), userGroups.end());
1295             usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1296                                         bus, objPath.c_str(), userGroups,
1297                                         userPriv, isUserEnabled(user), *this));
1298         }
1299     }
1300 }
1301 
1302 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1303     Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1304     pamPasswdConfigFile(defaultPamPasswdConfigFile),
1305     pamAuthConfigFile(defaultPamAuthConfigFile)
1306 {
1307     UserMgrIface::allPrivileges(privMgr);
1308     std::sort(groupsMgr.begin(), groupsMgr.end());
1309     UserMgrIface::allGroups(groupsMgr);
1310     initializeAccountPolicy();
1311     initUserObjects();
1312 
1313     // emit the signal
1314     this->emit_object_added();
1315 }
1316 
1317 void UserMgr::executeUserAdd(const char* userName, const char* groups,
1318                              bool sshRequested, bool enabled)
1319 {
1320     // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1321     // 1970-01-01, that's an implementation-defined behavior
1322     executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
1323                (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
1324                (enabled ? "" : "1970-01-01"));
1325 }
1326 
1327 void UserMgr::executeUserDelete(const char* userName)
1328 {
1329     executeCmd("/usr/sbin/userdel", userName, "-r");
1330 }
1331 
1332 void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1333 {
1334     std::string newHomeDir = "/home/";
1335     newHomeDir += newUserName;
1336     executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1337                newHomeDir.c_str(), "-m");
1338 }
1339 
1340 void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1341                                 bool sshRequested)
1342 {
1343     executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
1344                (sshRequested ? "/bin/sh" : "/bin/nologin"));
1345 }
1346 
1347 void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1348 {
1349     // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1350     // 1970-01-01, that's an implementation-defined behavior
1351     executeCmd("/usr/sbin/usermod", userName, "-e",
1352                (enabled ? "" : "1970-01-01"));
1353 }
1354 
1355 std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1356 {
1357     return executeCmd("/usr/sbin/pam_tally2", "-u", userName);
1358 }
1359 
1360 void UserMgr::createGroup(std::string /*groupName*/)
1361 {
1362     log<level::ERR>("Not implemented yet");
1363     elog<InternalFailure>();
1364 }
1365 
1366 void UserMgr::deleteGroup(std::string /*groupName*/)
1367 {
1368     log<level::ERR>("Not implemented yet");
1369     elog<InternalFailure>();
1370 }
1371 
1372 } // namespace user
1373 } // namespace phosphor
1374