xref: /openbmc/phosphor-user-manager/user_mgr.cpp (revision 2a137f4de7d678cdc87f5d6b62c85fa7b1d95ec1)
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 <phosphor-logging/elog-errors.hpp>
34 #include <phosphor-logging/elog.hpp>
35 #include <phosphor-logging/lg2.hpp>
36 #include <xyz/openbmc_project/Common/error.hpp>
37 #include <xyz/openbmc_project/User/Common/error.hpp>
38 
39 #include <algorithm>
40 #include <array>
41 #include <chrono>
42 #include <ctime>
43 #include <filesystem>
44 #include <fstream>
45 #include <numeric>
46 #include <regex>
47 #include <span>
48 #include <string>
49 #include <string_view>
50 #include <vector>
51 namespace phosphor
52 {
53 namespace user
54 {
55 
56 static constexpr const char* passwdFileName = "/etc/passwd";
57 static constexpr size_t ipmiMaxUserNameLen = 16;
58 static constexpr size_t systemMaxUserNameLen = 100;
59 static constexpr const char* grpSsh = "ssh";
60 static constexpr int success = 0;
61 static constexpr int failure = -1;
62 static constexpr long secondsPerDay = 60 * 60 * 24;
63 
64 uint8_t maxPasswdLength = MAX_PASSWORD_LENGTH;
65 // pam modules related
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* defaultFaillockConfigFile =
71     "/etc/security/faillock.conf";
72 static constexpr const char* defaultPWHistoryConfigFile =
73     "/etc/security/pwhistory.conf";
74 static constexpr const char* defaultPWQualityConfigFile =
75     "/etc/security/pwquality.conf";
76 
77 // Object Manager related
78 static constexpr const char* ldapMgrObjBasePath =
79     "/xyz/openbmc_project/user/ldap";
80 
81 // Object Mapper related
82 static constexpr const char* objMapperService =
83     "xyz.openbmc_project.ObjectMapper";
84 static constexpr const char* objMapperPath =
85     "/xyz/openbmc_project/object_mapper";
86 static constexpr const char* objMapperInterface =
87     "xyz.openbmc_project.ObjectMapper";
88 
89 using namespace phosphor::logging;
90 using InsufficientPermission =
91     sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
92 using InternalFailure =
93     sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
94 using InvalidArgument =
95     sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
96 using UserNameExists =
97     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
98 using UserNameDoesNotExist =
99     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
100 using UserNameGroupFail =
101     sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
102 using NoResource =
103     sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
104 using Argument = xyz::openbmc_project::Common::InvalidArgument;
105 using GroupNameExists =
106     sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameExists;
107 using GroupNameDoesNotExists =
108     sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameDoesNotExist;
109 using UserProperty =
110     sdbusplus::common::xyz::openbmc_project::user::Manager::UserProperty;
111 
112 namespace
113 {
114 constexpr auto mfaConfPath = "/var/lib/usr_mgr.conf";
115 // The hardcoded groups in OpenBMC projects
116 constexpr std::array<const char*, 4> predefinedGroups = {
117     "redfish", "ipmi", "ssh", "hostconsole"};
118 
119 // These prefixes are for Dynamic Redfish authorization. See
120 // https://github.com/openbmc/docs/blob/master/designs/redfish-authorization.md
121 
122 // Base role and base privileges are added by Redfish implementation (e.g.,
123 // BMCWeb) at compile time
124 constexpr std::array<const char*, 4> allowedGroupPrefix = {
125     "openbmc_rfr_",  // OpenBMC Redfish Base Role
126     "openbmc_rfp_",  // OpenBMC Redfish Base Privileges
127     "openbmc_orfr_", // OpenBMC Redfish OEM Role
128     "openbmc_orfp_", // OpenBMC Redfish OEM Privileges
129 };
130 
131 void checkAndThrowsForGroupChangeAllowed(const std::string& groupName)
132 {
133     bool allowed = false;
134     for (std::string_view prefix : allowedGroupPrefix)
135     {
136         if (groupName.starts_with(prefix))
137         {
138             allowed = true;
139             break;
140         }
141     }
142     if (!allowed)
143     {
144         lg2::error("Group name '{GROUP}' is not in the allowed list", "GROUP",
145                    groupName);
146         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
147                               Argument::ARGUMENT_VALUE(groupName.c_str()));
148     }
149 }
150 
151 long currentDate()
152 {
153     const auto date = std::chrono::duration_cast<std::chrono::days>(
154                           std::chrono::system_clock::now().time_since_epoch())
155                           .count();
156 
157     if (date > std::numeric_limits<long>::max())
158     {
159         return std::numeric_limits<long>::max();
160     }
161 
162     if (date < std::numeric_limits<long>::min())
163     {
164         return std::numeric_limits<long>::min();
165     }
166 
167     return date;
168 }
169 
170 uint64_t daysToSeconds(const uint64_t days)
171 {
172     const uint64_t dateSeconds =
173         std::chrono::duration_cast<std::chrono::seconds>(
174             std::chrono::days{days})
175             .count();
176 
177     return dateSeconds;
178 }
179 
180 uint64_t secondsToDays(const uint64_t seconds)
181 {
182     const uint64_t dateDays = seconds / secondsPerDay;
183 
184     return dateDays;
185 }
186 
187 } // namespace
188 
189 std::string getCSVFromVector(std::span<const std::string> vec)
190 {
191     if (vec.empty())
192     {
193         return "";
194     }
195     return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
196                            [](std::string&& val, std::string_view element) {
197                                val += ',';
198                                val += element;
199                                return val;
200                            });
201 }
202 
203 bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
204 {
205     std::string::size_type delStrPos = csvStr.find(delStr);
206     if (delStrPos != std::string::npos)
207     {
208         // need to also delete the comma char
209         if (delStrPos == 0)
210         {
211             csvStr.erase(delStrPos, delStr.size() + 1);
212         }
213         else
214         {
215             csvStr.erase(delStrPos - 1, delStr.size() + 1);
216         }
217         return true;
218     }
219     return false;
220 }
221 
222 bool UserMgr::isUserExist(const std::string& userName) const
223 {
224     if (userName.empty())
225     {
226         lg2::error("User name is empty");
227         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
228                               Argument::ARGUMENT_VALUE("Null"));
229     }
230     if (usersList.find(userName) == usersList.end())
231     {
232         return false;
233     }
234     return true;
235 }
236 
237 void UserMgr::throwForUserDoesNotExist(const std::string& userName) const
238 {
239     if (!isUserExist(userName))
240     {
241         lg2::error("User '{USERNAME}' does not exist", "USERNAME", userName);
242         elog<UserNameDoesNotExist>();
243     }
244 }
245 
246 void UserMgr::checkAndThrowForDisallowedGroupCreation(
247     const std::string& groupName)
248 {
249     if (groupName.size() > maxSystemGroupNameLength ||
250         !std::regex_match(groupName.c_str(),
251                           std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
252     {
253         lg2::error("Invalid group name '{GROUP}'", "GROUP", groupName);
254         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
255                               Argument::ARGUMENT_VALUE(groupName.c_str()));
256     }
257     checkAndThrowsForGroupChangeAllowed(groupName);
258 }
259 
260 void UserMgr::throwForUserExists(const std::string& userName)
261 {
262     if (isUserExist(userName))
263     {
264         lg2::error("User '{USERNAME}' already exists", "USERNAME", userName);
265         elog<UserNameExists>();
266     }
267 }
268 
269 void UserMgr::throwForUserNameConstraints(
270     const std::string& userName, const std::vector<std::string>& groupNames)
271 {
272     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
273         groupNames.end())
274     {
275         if (userName.length() > ipmiMaxUserNameLen)
276         {
277             lg2::error("User '{USERNAME}' exceeds IPMI username length limit "
278                        "({LENGTH} > {LIMIT})",
279                        "USERNAME", userName, "LENGTH", userName.length(),
280                        "LIMIT", ipmiMaxUserNameLen);
281             elog<UserNameGroupFail>(
282                 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
283                     "IPMI length"));
284         }
285     }
286     if (userName.length() > systemMaxUserNameLen)
287     {
288         lg2::error("User '{USERNAME}' exceeds system username length limit "
289                    "({LENGTH} > {LIMIT})",
290                    "USERNAME", userName, "LENGTH", userName.length(), "LIMIT",
291                    systemMaxUserNameLen);
292         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
293                               Argument::ARGUMENT_VALUE("Invalid length"));
294     }
295     if (!std::regex_match(userName.c_str(),
296                           std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
297     {
298         lg2::error("Invalid username '{USERNAME}'", "USERNAME", userName);
299         elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
300                               Argument::ARGUMENT_VALUE("Invalid data"));
301     }
302 }
303 
304 void UserMgr::throwForMaxGrpUserCount(
305     const std::vector<std::string>& groupNames)
306 {
307     if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
308         groupNames.end())
309     {
310         if (getIpmiUsersCount() >= ipmiMaxUsers)
311         {
312             lg2::error("IPMI user limit reached");
313             elog<NoResource>(
314                 xyz::openbmc_project::User::Common::NoResource::REASON(
315                     "IPMI user limit reached"));
316         }
317     }
318     else
319     {
320         if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
321                                         (maxSystemUsers - ipmiMaxUsers))
322         {
323             lg2::error("Non-ipmi User limit reached");
324             elog<NoResource>(
325                 xyz::openbmc_project::User::Common::NoResource::REASON(
326                     "Non-ipmi user limit reached"));
327         }
328     }
329     return;
330 }
331 
332 void UserMgr::throwForInvalidPrivilege(const std::string& priv)
333 {
334     if (!priv.empty() &&
335         (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
336     {
337         lg2::error("Invalid privilege '{PRIVILEGE}'", "PRIVILEGE", priv);
338         elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
339                               Argument::ARGUMENT_VALUE(priv.c_str()));
340     }
341 }
342 
343 void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
344 {
345     for (auto& group : groupNames)
346     {
347         if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
348             groupsMgr.end())
349         {
350             lg2::error("Invalid Group Name '{GROUPNAME}'", "GROUPNAME", group);
351             elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
352                                   Argument::ARGUMENT_VALUE(group.c_str()));
353         }
354     }
355 }
356 
357 std::vector<std::string> UserMgr::readAllGroupsOnSystem()
358 {
359     std::vector<std::string> allGroups = {predefinedGroups.begin(),
360                                           predefinedGroups.end()};
361     // rewinds to the beginning of the group database
362     setgrent();
363     struct group* gr = getgrent();
364     while (gr != nullptr)
365     {
366         std::string group(gr->gr_name);
367         for (std::string_view prefix : allowedGroupPrefix)
368         {
369             if (group.starts_with(prefix))
370             {
371                 allGroups.push_back(gr->gr_name);
372             }
373         }
374         gr = getgrent();
375     }
376     // close the group database
377     endgrent();
378     return allGroups;
379 }
380 
381 void UserMgr::createUserImpl(const std::string& userName, UserCreateMap props)
382 {
383     auto priv = std::get<std::string>(props[UserProperty::Privilege]);
384     auto enabled = std::get<bool>(props[UserProperty::Enabled]);
385     auto groupNames =
386         std::get<std::vector<std::string>>(props[UserProperty::GroupNames]);
387 
388     auto passwordExpiration = getDefaultPasswordExpiration();
389     if (props.contains(UserProperty::PasswordExpiration))
390         passwordExpiration =
391             std::get<uint64_t>(props[UserProperty::PasswordExpiration]);
392 
393     throwForInvalidPrivilege(priv);
394     throwForInvalidGroups(groupNames);
395     // All user management lock has to be based on /etc/shadow
396     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
397     throwForUserExists(userName);
398     throwForUserNameConstraints(userName, groupNames);
399     throwForMaxGrpUserCount(groupNames);
400 
401     std::string groups = getCSVFromVector(groupNames);
402     bool sshRequested = removeStringFromCSV(groups, grpSsh);
403 
404     // treat privilege as a group - This is to avoid using different file to
405     // store the same.
406     if (!priv.empty())
407     {
408         if (groups.size() != 0)
409         {
410             groups += ",";
411         }
412         groups += priv;
413     }
414     try
415     {
416         executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
417     }
418     catch (const InternalFailure& e)
419     {
420         lg2::error("Unable to create new user '{USERNAME}'", "USERNAME",
421                    userName);
422         elog<InternalFailure>();
423     }
424 
425     // Add the users object before sending out the signal
426     sdbusplus::message::object_path tempObjPath(usersObjPath);
427     tempObjPath /= userName;
428     std::string userObj(tempObjPath);
429     std::sort(groupNames.begin(), groupNames.end());
430     usersList.emplace(userName, std::make_unique<phosphor::user::Users>(
431                                     bus, userObj.c_str(), groupNames, priv,
432                                     enabled, passwordExpiration, *this));
433     serializer.store();
434     lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
435 
436     return;
437 }
438 
439 void UserMgr::createUser(std::string userName,
440                          std::vector<std::string> groupNames, std::string priv,
441                          bool enabled)
442 {
443     UserCreateMap props;
444     props[UserProperty::GroupNames] = std::move(groupNames);
445     props[UserProperty::Privilege] = std::move(priv);
446     props[UserProperty::Enabled] = enabled;
447 
448     createUserImpl(userName, props);
449     lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
450 }
451 
452 void UserMgr::deleteUserImpl(const std::string& userName)
453 {
454     // All user management lock has to be based on /etc/shadow
455     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
456     try
457     {
458         // Clear user fail records
459         executeUserClearFailRecords(userName.c_str());
460 
461         executeUserDelete(userName.c_str());
462     }
463     catch (const InternalFailure& e)
464     {
465         lg2::error("Delete User '{USERNAME}' failed", "USERNAME", userName);
466         elog<InternalFailure>();
467     }
468 
469     usersList.erase(userName);
470     serializer.store();
471     lg2::info("User '{USERNAME}' deleted successfully", "USERNAME", userName);
472     return;
473 }
474 
475 void UserMgr::deleteUser(std::string userName)
476 {
477     throwForUserDoesNotExist(userName);
478     deleteUserImpl(userName);
479     lg2::info("User '{USERNAME}' deleted successfully", "USERNAME", userName);
480 }
481 
482 void UserMgr::checkDeleteGroupConstraints(const std::string& groupName)
483 {
484     if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) ==
485         groupsMgr.end())
486     {
487         lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
488         elog<GroupNameDoesNotExists>();
489     }
490     checkAndThrowsForGroupChangeAllowed(groupName);
491 }
492 
493 void UserMgr::deleteGroup(std::string groupName)
494 {
495     checkDeleteGroupConstraints(groupName);
496     try
497     {
498         executeGroupDeletion(groupName.c_str());
499     }
500     catch (const InternalFailure& e)
501     {
502         lg2::error("Failed to delete group '{GROUP}'", "GROUP", groupName);
503         elog<InternalFailure>();
504     }
505 
506     groupsMgr.erase(std::find(groupsMgr.begin(), groupsMgr.end(), groupName));
507     UserMgrIface::allGroups(groupsMgr);
508     lg2::info("Successfully deleted group '{GROUP}'", "GROUP", groupName);
509 }
510 
511 void UserMgr::checkCreateGroupConstraints(const std::string& groupName)
512 {
513     if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) !=
514         groupsMgr.end())
515     {
516         lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
517         elog<GroupNameExists>();
518     }
519     checkAndThrowForDisallowedGroupCreation(groupName);
520     if (groupsMgr.size() >= maxSystemGroupCount)
521     {
522         lg2::error("Group limit reached");
523         elog<NoResource>(xyz::openbmc_project::User::Common::NoResource::REASON(
524             "Group limit reached"));
525     }
526 }
527 
528 void UserMgr::createGroup(std::string groupName)
529 {
530     checkCreateGroupConstraints(groupName);
531     try
532     {
533         executeGroupCreation(groupName.c_str());
534     }
535     catch (const InternalFailure& e)
536     {
537         lg2::error("Failed to create group '{GROUP}'", "GROUP", groupName);
538         elog<InternalFailure>();
539     }
540     groupsMgr.push_back(groupName);
541     UserMgrIface::allGroups(groupsMgr);
542 }
543 
544 void UserMgr::renameUser(std::string userName, std::string newUserName)
545 {
546     // All user management lock has to be based on /etc/shadow
547     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
548     throwForUserDoesNotExist(userName);
549     throwForUserExists(newUserName);
550     throwForUserNameConstraints(newUserName,
551                                 usersList[userName].get()->userGroups());
552     try
553     {
554         executeUserRename(userName.c_str(), newUserName.c_str());
555     }
556     catch (const InternalFailure& e)
557     {
558         lg2::error("Rename '{USERNAME}' to '{NEWUSERNAME}' failed", "USERNAME",
559                    userName, "NEWUSERNAME", newUserName);
560         elog<InternalFailure>();
561     }
562     const auto& user = usersList[userName];
563     std::string priv = user.get()->userPrivilege();
564     std::vector<std::string> groupNames = user.get()->userGroups();
565     bool enabled = user.get()->userEnabled();
566     uint64_t passwordExpiration = user.get()->passwordExpiration();
567     sdbusplus::message::object_path tempObjPath(usersObjPath);
568     tempObjPath /= newUserName;
569     std::string newUserObj(tempObjPath);
570     // Special group 'ipmi' needs a way to identify user renamed, in order to
571     // update encrypted password. It can't rely only on InterfacesRemoved &
572     // InterfacesAdded. So first send out userRenamed signal.
573     this->userRenamed(userName, newUserName);
574     usersList.erase(userName);
575     usersList.emplace(newUserName,
576                       std::make_unique<phosphor::user::Users>(
577                           bus, newUserObj.c_str(), groupNames, priv, enabled,
578                           passwordExpiration, *this));
579     return;
580 }
581 
582 void UserMgr::updateGroupsAndPriv(const std::string& userName,
583                                   std::vector<std::string> groupNames,
584                                   const std::string& priv)
585 {
586     throwForInvalidPrivilege(priv);
587     throwForInvalidGroups(groupNames);
588     // All user management lock has to be based on /etc/shadow
589     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
590     throwForUserDoesNotExist(userName);
591     const std::vector<std::string>& oldGroupNames =
592         usersList[userName].get()->userGroups();
593     std::vector<std::string> groupDiff;
594     // Note: already dealing with sorted group lists.
595     std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
596                                   groupNames.begin(), groupNames.end(),
597                                   std::back_inserter(groupDiff));
598     if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
599         groupDiff.end())
600     {
601         throwForUserNameConstraints(userName, groupNames);
602         throwForMaxGrpUserCount(groupNames);
603     }
604 
605     std::string groups = getCSVFromVector(groupNames);
606     bool sshRequested = removeStringFromCSV(groups, grpSsh);
607 
608     // treat privilege as a group - This is to avoid using different file to
609     // store the same.
610     if (!priv.empty())
611     {
612         if (groups.size() != 0)
613         {
614             groups += ",";
615         }
616         groups += priv;
617     }
618     try
619     {
620         executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
621     }
622     catch (const InternalFailure& e)
623     {
624         lg2::error(
625             "Unable to modify user privilege / groups for user '{USERNAME}'",
626             "USERNAME", userName);
627         elog<InternalFailure>();
628     }
629 
630     std::sort(groupNames.begin(), groupNames.end());
631     usersList[userName]->setUserGroups(groupNames);
632     usersList[userName]->setUserPrivilege(priv);
633     lg2::info("User '{USERNAME}' groups / privilege updated successfully",
634               "USERNAME", userName);
635 }
636 
637 uint8_t UserMgr::minPasswordLength(uint8_t value)
638 {
639     if (value == AccountPolicyIface::minPasswordLength())
640     {
641         return value;
642     }
643     if (value < minPasswdLength || value > maxPasswdLength)
644     {
645         std::string valueStr = std::to_string(value);
646         lg2::error("Attempting to set minPasswordLength to {VALUE}, less than "
647                    "{MINPASSWORDLENGTH} or greater than {MAXPASSWORDLENGTH}",
648                    "VALUE", value, "MINPASSWORDLENGTH", minPasswdLength,
649                    "MAXPASSWORDLENGTH", maxPasswdLength);
650         elog<InvalidArgument>(Argument::ARGUMENT_NAME("minPasswordLength"),
651                               Argument::ARGUMENT_VALUE(valueStr.data()));
652     }
653     if (setPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
654                               std::to_string(value)) != success)
655     {
656         lg2::error("Unable to set minPasswordLength to {VALUE}", "VALUE",
657                    value);
658         elog<InternalFailure>();
659     }
660     return AccountPolicyIface::minPasswordLength(value);
661 }
662 
663 uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
664 {
665     if (value == AccountPolicyIface::rememberOldPasswordTimes())
666     {
667         return value;
668     }
669     if (setPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
670                               std::to_string(value)) != success)
671     {
672         lg2::error("Unable to set rememberOldPasswordTimes to {VALUE}", "VALUE",
673                    value);
674         elog<InternalFailure>();
675     }
676     return AccountPolicyIface::rememberOldPasswordTimes(value);
677 }
678 
679 uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
680 {
681     if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
682     {
683         return value;
684     }
685     if (setPamModuleConfValue(faillockConfigFile, maxFailedAttempt,
686                               std::to_string(value)) != success)
687     {
688         lg2::error("Unable to set maxLoginAttemptBeforeLockout to {VALUE}",
689                    "VALUE", value);
690         elog<InternalFailure>();
691     }
692     return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
693 }
694 
695 uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
696 {
697     if (value == AccountPolicyIface::accountUnlockTimeout())
698     {
699         return value;
700     }
701     if (setPamModuleConfValue(faillockConfigFile, unlockTimeout,
702                               std::to_string(value)) != success)
703     {
704         lg2::error("Unable to set accountUnlockTimeout to {VALUE}", "VALUE",
705                    value);
706         elog<InternalFailure>();
707     }
708     return AccountPolicyIface::accountUnlockTimeout(value);
709 }
710 
711 int UserMgr::getPamModuleConfValue(const std::string& confFile,
712                                    const std::string& argName,
713                                    std::string& argValue)
714 {
715     std::ifstream fileToRead(confFile, std::ios::in);
716     if (!fileToRead.is_open())
717     {
718         lg2::error("Failed to open pam configuration file {FILENAME}",
719                    "FILENAME", confFile);
720         return failure;
721     }
722     std::string line;
723     auto argSearch = argName + "=";
724     size_t startPos = 0;
725     size_t endPos = 0;
726     while (getline(fileToRead, line))
727     {
728         // skip comments section starting with #
729         if ((startPos = line.find('#')) != std::string::npos)
730         {
731             if (startPos == 0)
732             {
733                 continue;
734             }
735             // skip comments after meaningful section and process those
736             line = line.substr(0, startPos);
737         }
738         if ((startPos = line.find(argSearch)) != std::string::npos)
739         {
740             if ((endPos = line.find(' ', startPos)) == std::string::npos)
741             {
742                 endPos = line.size();
743             }
744             startPos += argSearch.size();
745             argValue = line.substr(startPos, endPos - startPos);
746             return success;
747         }
748     }
749     return failure;
750 }
751 
752 int UserMgr::setPamModuleConfValue(const std::string& confFile,
753                                    const std::string& argName,
754                                    const std::string& argValue)
755 {
756     std::string tmpConfFile = confFile + "_tmp";
757     std::ifstream fileToRead(confFile, std::ios::in);
758     std::ofstream fileToWrite(tmpConfFile, std::ios::out);
759     if (!fileToRead.is_open() || !fileToWrite.is_open())
760     {
761         lg2::error("Failed to open pam configuration file {FILENAME}",
762                    "FILENAME", confFile);
763         // Delete the unused tmp file
764         std::remove(tmpConfFile.c_str());
765         return failure;
766     }
767     std::string line;
768     auto argSearch = argName + "=";
769     size_t startPos = 0;
770     size_t endPos = 0;
771     bool found = false;
772     while (getline(fileToRead, line))
773     {
774         // skip comments section starting with #
775         if ((startPos = line.find('#')) != std::string::npos)
776         {
777             if (startPos == 0)
778             {
779                 fileToWrite << line << std::endl;
780                 continue;
781             }
782             // skip comments after meaningful section and process those
783             line = line.substr(0, startPos);
784         }
785         if ((startPos = line.find(argSearch)) != std::string::npos)
786         {
787             if ((endPos = line.find(' ', startPos)) == std::string::npos)
788             {
789                 endPos = line.size();
790             }
791             startPos += argSearch.size();
792             fileToWrite << line.substr(0, startPos) << argValue
793                         << line.substr(endPos, line.size() - endPos)
794                         << std::endl;
795             found = true;
796             continue;
797         }
798         fileToWrite << line << std::endl;
799     }
800     fileToWrite.close();
801     fileToRead.close();
802     if (found)
803     {
804         if (std::rename(tmpConfFile.c_str(), confFile.c_str()) == 0)
805         {
806             return success;
807         }
808     }
809     // No changes, so delete the unused tmp file
810     std::remove(tmpConfFile.c_str());
811     return failure;
812 }
813 
814 void UserMgr::userEnable(const std::string& userName, bool enabled)
815 {
816     // All user management lock has to be based on /etc/shadow
817     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
818     throwForUserDoesNotExist(userName);
819     try
820     {
821         executeUserModifyUserEnable(userName.c_str(), enabled);
822     }
823     catch (const InternalFailure& e)
824     {
825         lg2::error("Unable to modify user enabled state for '{USERNAME}'",
826                    "USERNAME", userName);
827         elog<InternalFailure>();
828     }
829 
830     usersList[userName]->setUserEnabled(enabled);
831     lg2::info("User '{USERNAME}' has been {STATUS}", "USERNAME", userName,
832               "STATUS", enabled ? "Enabled" : "Disabled");
833 }
834 
835 /**
836  * faillock app will provide the user failed login list with when the attempt
837  * was made, the type, the source, and if it's valid.
838  *
839  * Valid in this case means that the attempt was made within the fail_interval
840  * time. So, we can check this list for the number of valid entries (lines
841  * ending with 'V') compared to the maximum allowed to determine if the user is
842  * locked out.
843  *
844  * This data is only refreshed when an attempt is made, so if the user appears
845  * to be locked out, we must also check if the most recent attempt was older
846  * than the unlock_time to know if the user has since been unlocked.
847  **/
848 bool UserMgr::parseFaillockForLockout(
849     const std::vector<std::string>& faillockOutput)
850 {
851     uint16_t failAttempts = 0;
852     time_t lastFailedAttempt{};
853     for (const std::string& line : faillockOutput)
854     {
855         if (!line.ends_with("V"))
856         {
857             continue;
858         }
859 
860         // Count this failed attempt
861         failAttempts++;
862 
863         // Update the last attempt time
864         // First get the "when" which is the first two words (date and time)
865         size_t pos = line.find(" ");
866         if (pos == std::string::npos)
867         {
868             continue;
869         }
870         pos = line.find(" ", pos + 1);
871         if (pos == std::string::npos)
872         {
873             continue;
874         }
875         std::string failDateTime = line.substr(0, pos);
876 
877         // NOTE: Cannot use std::get_time() here as the implementation of %y in
878         // libstdc++ does not match POSIX strptime() before gcc 12.1.0
879         // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
880         std::tm tmStruct = {};
881         if (!strptime(failDateTime.c_str(), "%F %T", &tmStruct))
882         {
883             lg2::error("Failed to parse latest failure date/time");
884             elog<InternalFailure>();
885         }
886 
887         time_t failTimestamp = std::mktime(&tmStruct);
888         lastFailedAttempt = std::max(failTimestamp, lastFailedAttempt);
889     }
890 
891     if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
892     {
893         return false;
894     }
895 
896     if (lastFailedAttempt +
897             static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
898         std::time(NULL))
899     {
900         return false;
901     }
902 
903     return true;
904 }
905 
906 bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
907 {
908     // All user management lock has to be based on /etc/shadow
909     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
910     if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
911     {
912         return false;
913     }
914 
915     std::vector<std::string> output;
916     try
917     {
918         output = getFailedAttempt(userName.c_str());
919     }
920     catch (const InternalFailure& e)
921     {
922         lg2::error("Unable to read login failure counter");
923         elog<InternalFailure>();
924     }
925 
926     return parseFaillockForLockout(output);
927 }
928 
929 bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
930                                          const bool& value)
931 {
932     // All user management lock has to be based on /etc/shadow
933     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
934     if (value == true)
935     {
936         return userLockedForFailedAttempt(userName);
937     }
938 
939     try
940     {
941         // Clear user fail records
942         executeUserClearFailRecords(userName.c_str());
943     }
944     catch (const InternalFailure& e)
945     {
946         lg2::error("Unable to reset login failure counter");
947         elog<InternalFailure>();
948     }
949 
950     return userLockedForFailedAttempt(userName);
951 }
952 
953 bool UserMgr::userPasswordExpired(const std::string& userName)
954 {
955     // All user management lock has to be based on /etc/shadow
956     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
957 
958     struct spwd spwd{};
959     struct spwd* spwdPtr = nullptr;
960     auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
961     if (buflen <= 0)
962     {
963         // Use a default size if there is no hard limit suggested by sysconf()
964         buflen = 1024;
965     }
966     std::vector<char> buffer(buflen);
967     auto status =
968         getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
969     // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
970     // If no matching password record was found, these functions return 0
971     // and store NULL in *spwdPtr
972     if ((status == 0) && (&spwd == spwdPtr))
973     {
974         // Determine password validity per "chage" docs, where:
975         //   spwd.sp_lstchg == 0 means password is expired, and
976         //   spwd.sp_max == -1 means the password does not expire.
977         long today = static_cast<long>(time(NULL)) / secondsPerDay;
978         if ((spwd.sp_lstchg == 0) ||
979             ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
980         {
981             return true;
982         }
983     }
984     else
985     {
986         // User entry is missing in /etc/shadow, indicating no SHA password.
987         // Treat this as new user without password entry in /etc/shadow
988         // TODO: Add property to indicate user password was not set yet
989         // https://github.com/openbmc/phosphor-user-manager/issues/8
990         return false;
991     }
992 
993     return false;
994 }
995 
996 UserSSHLists UserMgr::getUserAndSshGrpList()
997 {
998     // All user management lock has to be based on /etc/shadow
999     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1000 
1001     std::vector<std::string> userList;
1002     std::vector<std::string> sshUsersList;
1003     struct passwd pw, *pwp = nullptr;
1004     std::array<char, 1024> buffer{};
1005 
1006     phosphor::user::File passwd(passwdFileName, "r");
1007     if ((passwd)() == NULL)
1008     {
1009         lg2::error("Error opening {FILENAME}", "FILENAME", passwdFileName);
1010         elog<InternalFailure>();
1011     }
1012 
1013     while (true)
1014     {
1015         auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
1016                              &pwp);
1017         if ((r != 0) || (pwp == NULL))
1018         {
1019             // Any error, break the loop.
1020             break;
1021         }
1022 #ifdef ENABLE_ROOT_USER_MGMT
1023         // Add all users whose UID >= 1000 and < 65534
1024         // and special UID 0.
1025         if ((pwp->pw_uid == 0) ||
1026             ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
1027 #else
1028         // Add all users whose UID >=1000 and < 65534
1029         if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
1030 #endif
1031         {
1032             std::string userName(pwp->pw_name);
1033             userList.emplace_back(userName);
1034 
1035             // ssh doesn't have separate group. Check login shell entry to
1036             // get all users list which are member of ssh group.
1037             std::string loginShell(pwp->pw_shell);
1038             if (loginShell == "/bin/sh")
1039             {
1040                 sshUsersList.emplace_back(userName);
1041             }
1042         }
1043     }
1044     endpwent();
1045     return std::make_pair(std::move(userList), std::move(sshUsersList));
1046 }
1047 
1048 size_t UserMgr::getIpmiUsersCount()
1049 {
1050     std::vector<std::string> userList = getUsersInGroup("ipmi");
1051     return userList.size();
1052 }
1053 
1054 size_t UserMgr::getNonIpmiUsersCount()
1055 {
1056     std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
1057     return usersList.size() - ipmiUsers.size();
1058 }
1059 
1060 bool UserMgr::isUserEnabled(const std::string& userName)
1061 {
1062     // All user management lock has to be based on /etc/shadow
1063     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1064     std::array<char, 4096> buffer{};
1065     struct spwd spwd;
1066     struct spwd* resultPtr = nullptr;
1067     int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
1068                             buffer.max_size(), &resultPtr);
1069     if (!status && (&spwd == resultPtr))
1070     {
1071         // according to chage/usermod code -1 means that account does not expire
1072         // https://github.com/shadow-maint/shadow/blob/7a796897e52293efe9e210ab8da32b7aefe65591/src/chage.c
1073         if (resultPtr->sp_expire < 0)
1074         {
1075             return true;
1076         }
1077 
1078         // check account expiration date against current date
1079         if (resultPtr->sp_expire > currentDate())
1080         {
1081             return true;
1082         }
1083 
1084         return false;
1085     }
1086     return false; // assume user is disabled for any error.
1087 }
1088 
1089 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
1090 {
1091     std::vector<std::string> usersInGroup;
1092     // Should be more than enough to get the pwd structure.
1093     std::array<char, 4096> buffer{};
1094     struct group grp;
1095     struct group* resultPtr = nullptr;
1096 
1097     int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1098                             buffer.max_size(), &resultPtr);
1099 
1100     if (!status && (&grp == resultPtr))
1101     {
1102         for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
1103         {
1104             usersInGroup.emplace_back(*(grp.gr_mem));
1105         }
1106     }
1107     else
1108     {
1109         lg2::error("Group '{GROUPNAME}' not found", "GROUPNAME", groupName);
1110         // Don't throw error, just return empty userList - fallback
1111     }
1112     return usersInGroup;
1113 }
1114 
1115 DbusUserObj UserMgr::getPrivilegeMapperObject(void)
1116 {
1117     DbusUserObj objects;
1118     try
1119     {
1120         std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
1121         std::string interface = "xyz.openbmc_project.User.Ldap.Config";
1122 
1123         auto ldapMgmtService =
1124             getServiceName(std::move(basePath), std::move(interface));
1125         auto method = bus.new_method_call(
1126             ldapMgmtService.c_str(), ldapMgrObjBasePath,
1127             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1128 
1129         auto reply = bus.call(method);
1130         reply.read(objects);
1131     }
1132     catch (const InternalFailure& e)
1133     {
1134         lg2::error("Unable to get the User Service: {ERR}", "ERR", e);
1135         throw;
1136     }
1137     catch (const sdbusplus::exception_t& e)
1138     {
1139         lg2::error("Failed to execute GetManagedObjects at {PATH}: {ERR}",
1140                    "PATH", ldapMgrObjBasePath, "ERR", e);
1141         throw;
1142     }
1143     return objects;
1144 }
1145 
1146 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
1147 {
1148     auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1149                                           objMapperInterface, "GetObject");
1150 
1151     mapperCall.append(std::move(path));
1152     mapperCall.append(std::vector<std::string>({std::move(intf)}));
1153 
1154     std::map<std::string, std::vector<std::string>> mapperResponse;
1155     try
1156     {
1157         auto mapperResponseMsg = bus.call(mapperCall);
1158         mapperResponseMsg.read(mapperResponse);
1159     }
1160     catch (const sdbusplus::exception_t& e)
1161     {
1162         lg2::error("Error in mapper call: {ERROR}", "ERROR", e.what());
1163         elog<InternalFailure>();
1164     }
1165 
1166     if (mapperResponse.begin() == mapperResponse.end())
1167     {
1168         lg2::error("Invalid response from mapper");
1169         elog<InternalFailure>();
1170     }
1171 
1172     return mapperResponse.begin()->first;
1173 }
1174 
1175 gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
1176 {
1177     static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1178     if (buflen <= 0)
1179     {
1180         // Use a default size if there is no hard limit suggested by sysconf()
1181         buflen = 1024;
1182     }
1183 
1184     struct passwd pwd;
1185     struct passwd* pwdPtr = nullptr;
1186     std::vector<char> buffer(buflen);
1187 
1188     auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(),
1189                              buffer.size(), &pwdPtr);
1190     // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
1191     // If no matching password record was found, these functions return 0
1192     // and store NULL in *pwdPtr
1193     if (!status && (&pwd == pwdPtr))
1194     {
1195         return pwd.pw_gid;
1196     }
1197 
1198     lg2::error("User {USERNAME} does not exist", "USERNAME", userName);
1199     elog<UserNameDoesNotExist>();
1200 }
1201 
1202 bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
1203                             const std::string& groupName) const
1204 {
1205     static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1206     if (buflen <= 0)
1207     {
1208         // Use a default size if there is no hard limit suggested by sysconf()
1209         buflen = 1024;
1210     }
1211 
1212     struct group grp;
1213     struct group* grpPtr = nullptr;
1214     std::vector<char> buffer(buflen);
1215 
1216     auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1217                              buffer.size(), &grpPtr);
1218 
1219     // Groups with a lot of members may require a buffer of bigger size than
1220     // suggested by _SC_GETGR_R_SIZE_MAX.
1221     // 32K should be enough for about 2K members.
1222     constexpr auto maxBufferLength = 32 * 1024;
1223     while (status == ERANGE && buflen < maxBufferLength)
1224     {
1225         buflen *= 2;
1226         buffer.resize(buflen);
1227 
1228         lg2::debug("Increase buffer for getgrnam_r() to {SIZE}", "SIZE",
1229                    buflen);
1230 
1231         status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1232                             buffer.size(), &grpPtr);
1233     }
1234 
1235     // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
1236     // If no matching group record was found, these functions return 0
1237     // and store NULL in *grpPtr
1238     if (!status && (&grp == grpPtr))
1239     {
1240         if (primaryGid == grp.gr_gid)
1241         {
1242             return true;
1243         }
1244 
1245         for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
1246         {
1247             if (userName == grp.gr_mem[i])
1248             {
1249                 return true;
1250             }
1251         }
1252     }
1253     else if (status == ERANGE)
1254     {
1255         lg2::error("Group info of {GROUP} requires too much memory", "GROUP",
1256                    groupName);
1257     }
1258     else
1259     {
1260         lg2::error("Group {GROUP} does not exist", "GROUP", groupName);
1261     }
1262 
1263     return false;
1264 }
1265 
1266 void UserMgr::executeGroupCreation(const char* groupName)
1267 {
1268     executeCmd("/usr/sbin/groupadd", groupName);
1269 }
1270 
1271 void UserMgr::executeGroupDeletion(const char* groupName)
1272 {
1273     executeCmd("/usr/sbin/groupdel", groupName);
1274 }
1275 
1276 UserInfoMap UserMgr::getUserInfo(std::string userName)
1277 {
1278     UserInfoMap userInfo;
1279     // Check whether the given user is local user or not.
1280     if (isUserExist(userName))
1281     {
1282         const auto& user = usersList[userName];
1283         userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1284         userInfo.emplace("UserGroups", user.get()->userGroups());
1285         userInfo.emplace("UserEnabled", user.get()->userEnabled());
1286         userInfo.emplace("UserLockedForFailedAttempt",
1287                          user.get()->userLockedForFailedAttempt());
1288         userInfo.emplace("UserPasswordExpired",
1289                          user.get()->userPasswordExpired());
1290         userInfo.emplace("TOTPSecretkeyRequired",
1291                          user.get()->secretKeyGenerationRequired());
1292         userInfo.emplace("PasswordExpiration",
1293                          user.get()->passwordExpiration());
1294         userInfo.emplace("RemoteUser", false);
1295     }
1296     else
1297     {
1298         auto primaryGid = getPrimaryGroup(userName);
1299 
1300         DbusUserObj objects = getPrivilegeMapperObject();
1301 
1302         std::string ldapConfigPath;
1303         std::string userPrivilege;
1304 
1305         try
1306         {
1307             for (const auto& [path, interfaces] : objects)
1308             {
1309                 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
1310                 if (it != interfaces.end())
1311                 {
1312                     auto propIt = it->second.find("Enabled");
1313                     if (propIt != it->second.end() &&
1314                         std::get<bool>(propIt->second))
1315                     {
1316                         ldapConfigPath = path.str + '/';
1317                         break;
1318                     }
1319                 }
1320             }
1321 
1322             if (ldapConfigPath.empty())
1323             {
1324                 return userInfo;
1325             }
1326 
1327             for (const auto& [path, interfaces] : objects)
1328             {
1329                 if (!path.str.starts_with(ldapConfigPath))
1330                 {
1331                     continue;
1332                 }
1333 
1334                 auto it = interfaces.find(
1335                     "xyz.openbmc_project.User.PrivilegeMapperEntry");
1336                 if (it != interfaces.end())
1337                 {
1338                     std::string privilege;
1339                     std::string groupName;
1340 
1341                     for (const auto& [propName, propValue] : it->second)
1342                     {
1343                         if (propName == "GroupName")
1344                         {
1345                             groupName = std::get<std::string>(propValue);
1346                         }
1347                         else if (propName == "Privilege")
1348                         {
1349                             privilege = std::get<std::string>(propValue);
1350                         }
1351                     }
1352 
1353                     if (!groupName.empty() && !privilege.empty() &&
1354                         isGroupMember(userName, primaryGid, groupName))
1355                     {
1356                         userPrivilege = privilege;
1357                         break;
1358                     }
1359                 }
1360                 if (!userPrivilege.empty())
1361                 {
1362                     break;
1363                 }
1364             }
1365 
1366             if (userPrivilege.empty())
1367             {
1368                 lg2::warning("LDAP group privilege mapping does not exist");
1369             }
1370             userInfo.emplace("UserPrivilege", userPrivilege);
1371         }
1372         catch (const std::bad_variant_access& e)
1373         {
1374             lg2::error("Error while accessing variant: {ERR}", "ERR", e);
1375             elog<InternalFailure>();
1376         }
1377         userInfo.emplace("RemoteUser", true);
1378     }
1379 
1380     return userInfo;
1381 }
1382 
1383 void UserMgr::initializeAccountPolicy()
1384 {
1385     std::string valueStr;
1386     auto value = minPasswdLength;
1387     unsigned long tmp = 0;
1388     if (getPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
1389                               valueStr) != success)
1390     {
1391         AccountPolicyIface::minPasswordLength(minPasswdLength);
1392     }
1393     else
1394     {
1395         try
1396         {
1397             tmp = std::stoul(valueStr, nullptr);
1398             if (tmp > std::numeric_limits<decltype(value)>::max())
1399             {
1400                 throw std::out_of_range("Out of range");
1401             }
1402             value = static_cast<decltype(value)>(tmp);
1403         }
1404         catch (const std::exception& e)
1405         {
1406             lg2::error("Exception for MinPasswordLength: {ERR}", "ERR", e);
1407             throw;
1408         }
1409         AccountPolicyIface::minPasswordLength(value);
1410     }
1411     valueStr.clear();
1412     if (getPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
1413                               valueStr) != success)
1414     {
1415         AccountPolicyIface::rememberOldPasswordTimes(0);
1416     }
1417     else
1418     {
1419         value = 0;
1420         try
1421         {
1422             tmp = std::stoul(valueStr, nullptr);
1423             if (tmp > std::numeric_limits<decltype(value)>::max())
1424             {
1425                 throw std::out_of_range("Out of range");
1426             }
1427             value = static_cast<decltype(value)>(tmp);
1428         }
1429         catch (const std::exception& e)
1430         {
1431             lg2::error("Exception for RememberOldPasswordTimes: {ERR}", "ERR",
1432                        e);
1433             throw;
1434         }
1435         AccountPolicyIface::rememberOldPasswordTimes(value);
1436     }
1437     valueStr.clear();
1438     if (getPamModuleConfValue(faillockConfigFile, maxFailedAttempt, valueStr) !=
1439         success)
1440     {
1441         AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1442     }
1443     else
1444     {
1445         uint16_t value16 = 0;
1446         try
1447         {
1448             tmp = std::stoul(valueStr, nullptr);
1449             if (tmp > std::numeric_limits<decltype(value16)>::max())
1450             {
1451                 throw std::out_of_range("Out of range");
1452             }
1453             value16 = static_cast<decltype(value16)>(tmp);
1454         }
1455         catch (const std::exception& e)
1456         {
1457             lg2::error("Exception for MaxLoginAttemptBeforLockout: {ERR}",
1458                        "ERR", e);
1459             throw;
1460         }
1461         AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1462     }
1463     valueStr.clear();
1464     if (getPamModuleConfValue(faillockConfigFile, unlockTimeout, valueStr) !=
1465         success)
1466     {
1467         AccountPolicyIface::accountUnlockTimeout(0);
1468     }
1469     else
1470     {
1471         uint32_t value32 = 0;
1472         try
1473         {
1474             tmp = std::stoul(valueStr, nullptr);
1475             if (tmp > std::numeric_limits<decltype(value32)>::max())
1476             {
1477                 throw std::out_of_range("Out of range");
1478             }
1479             value32 = static_cast<decltype(value32)>(tmp);
1480         }
1481         catch (const std::exception& e)
1482         {
1483             lg2::error("Exception for AccountUnlockTimeout: {ERR}", "ERR", e);
1484             throw;
1485         }
1486         AccountPolicyIface::accountUnlockTimeout(value32);
1487     }
1488 }
1489 
1490 void UserMgr::initUserObjects(void)
1491 {
1492     // All user management lock has to be based on /etc/shadow
1493     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1494     std::vector<std::string> userNameList;
1495     std::vector<std::string> sshGrpUsersList;
1496     UserSSHLists userSSHLists = getUserAndSshGrpList();
1497     userNameList = std::move(userSSHLists.first);
1498     sshGrpUsersList = std::move(userSSHLists.second);
1499 
1500     if (!userNameList.empty())
1501     {
1502         std::map<std::string, std::vector<std::string>> groupLists;
1503         // We only track users that are in the |predefinedGroups|
1504         // The other groups don't contain real BMC users.
1505         for (const char* grp : predefinedGroups)
1506         {
1507             if (grp == grpSsh)
1508             {
1509                 groupLists.emplace(grp, sshGrpUsersList);
1510             }
1511             else
1512             {
1513                 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1514                 groupLists.emplace(grp, grpUsersList);
1515             }
1516         }
1517         for (auto& grp : privMgr)
1518         {
1519             std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1520             groupLists.emplace(grp, grpUsersList);
1521         }
1522 
1523         for (auto& user : userNameList)
1524         {
1525             std::vector<std::string> userGroups;
1526             std::string userPriv;
1527             for (const auto& grp : groupLists)
1528             {
1529                 std::vector<std::string> tempGrp = grp.second;
1530                 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1531                     tempGrp.end())
1532                 {
1533                     if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1534                         privMgr.end())
1535                     {
1536                         userPriv = grp.first;
1537                     }
1538                     else
1539                     {
1540                         userGroups.emplace_back(grp.first);
1541                     }
1542                 }
1543             }
1544             // Add user objects to the Users path.
1545             sdbusplus::message::object_path tempObjPath(usersObjPath);
1546             tempObjPath /= user;
1547             std::string objPath(tempObjPath);
1548             std::sort(userGroups.begin(), userGroups.end());
1549 
1550             usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1551                                         bus, objPath.c_str(), userGroups,
1552                                         userPriv, isUserEnabled(user),
1553                                         getPasswordExpiration(user), *this));
1554         }
1555     }
1556 }
1557 
1558 void UserMgr::load()
1559 {
1560     std::optional<std::string> authTypeStr;
1561     if (std::filesystem::exists(mfaConfPath) && serializer.load())
1562     {
1563         serializer.deserialize("authtype", authTypeStr);
1564     }
1565     auto authType =
1566         authTypeStr.transform(MultiFactorAuthConfiguration::convertStringToType)
1567             .value_or(std::optional(MultiFactorAuthType::None));
1568     if (authType)
1569     {
1570         enabled(*authType, true);
1571     }
1572 }
1573 
1574 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1575     Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1576     serializer(mfaConfPath), faillockConfigFile(defaultFaillockConfigFile),
1577     pwHistoryConfigFile(defaultPWHistoryConfigFile),
1578     pwQualityConfigFile(defaultPWQualityConfigFile)
1579 
1580 {
1581     UserMgrIface::allPrivileges(privMgr);
1582     groupsMgr = readAllGroupsOnSystem();
1583     std::sort(groupsMgr.begin(), groupsMgr.end());
1584     UserMgrIface::allGroups(groupsMgr);
1585     initializeAccountPolicy();
1586     load();
1587     initUserObjects();
1588     // emit the signal
1589     this->emit_object_added();
1590 }
1591 
1592 void UserMgr::executeUserAdd(const char* userName, const char* groups,
1593                              bool sshRequested, bool enabled)
1594 {
1595     // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1596     // 1970-01-01, that's an implementation-defined behavior
1597     executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
1598                (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
1599                (enabled ? "" : "1970-01-01"));
1600 }
1601 
1602 void UserMgr::executeUserDelete(const char* userName)
1603 {
1604     executeCmd("/usr/sbin/userdel", userName, "-r");
1605 }
1606 
1607 void UserMgr::executeUserClearFailRecords(const char* userName)
1608 {
1609     executeCmd("/usr/sbin/faillock", "--user", userName, "--reset");
1610 }
1611 
1612 void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1613 {
1614     std::string newHomeDir = "/home/";
1615     newHomeDir += newUserName;
1616     executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1617                newHomeDir.c_str(), "-m");
1618 }
1619 
1620 void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1621                                 bool sshRequested)
1622 {
1623     executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
1624                (sshRequested ? "/bin/sh" : "/sbin/nologin"));
1625 }
1626 
1627 void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1628 {
1629     // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1630     // 1970-01-01, that's an implementation-defined behavior
1631     executeCmd("/usr/sbin/usermod", userName, "-e",
1632                (enabled ? "" : "1970-01-01"));
1633 }
1634 
1635 std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1636 {
1637     return executeCmd("/usr/sbin/faillock", "--user", userName);
1638 }
1639 
1640 MultiFactorAuthType UserMgr::enabled(MultiFactorAuthType value, bool skipSignal)
1641 {
1642     if (value == enabled())
1643     {
1644         return value;
1645     }
1646     switch (value)
1647     {
1648         case MultiFactorAuthType::None:
1649             for (auto type : {MultiFactorAuthType::GoogleAuthenticator})
1650             {
1651                 for (auto& u : usersList)
1652                 {
1653                     u.second->enableMultiFactorAuth(type, false);
1654                 }
1655             }
1656             break;
1657         default:
1658             for (auto& u : usersList)
1659             {
1660                 u.second->enableMultiFactorAuth(value, true);
1661             }
1662             break;
1663     }
1664     serializer.serialize(
1665         "authtype", MultiFactorAuthConfiguration::convertTypeToString(value));
1666     serializer.store();
1667     return MultiFactorAuthConfigurationIface::enabled(value, skipSignal);
1668 }
1669 
1670 bool UserMgr::secretKeyRequired(std::string userName)
1671 {
1672     if (usersList.contains(userName))
1673     {
1674         return usersList[userName]->secretKeyGenerationRequired();
1675     }
1676     return false;
1677 }
1678 
1679 void UserMgr::executeUserPasswordExpiration(const char* userName,
1680                                             const long int passwordLastChange,
1681                                             const long int passwordAge) const
1682 {
1683     executeCmd("/usr/bin/chage", userName, "--lastday",
1684                std::to_string(passwordLastChange).c_str(), "--maxdays",
1685                std::to_string(passwordAge).c_str());
1686 }
1687 
1688 void UserMgr::getShadowData(const std::string& userName,
1689                             struct spwd& spwd) const
1690 {
1691     struct spwd* p = nullptr;
1692 
1693     auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1694     if (buflen <= 0)
1695         buflen = 1024;
1696 
1697     std::vector<char> buffer(buflen);
1698     auto status =
1699         getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &p);
1700     if (status)
1701     {
1702         lg2::warning("Failed to get shadow entry for the user {USER_NAME}",
1703                      "USER_NAME", userName.c_str());
1704         elog<InternalFailure>();
1705     }
1706 
1707     spwd.sp_namp = nullptr;
1708     spwd.sp_pwdp = nullptr;
1709 }
1710 
1711 uint64_t UserMgr::getPasswordExpiration(const std::string& userName) const
1712 {
1713     // All user management lock has to be based on /etc/shadow
1714     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1715     struct spwd spwd{};
1716     getShadowData(userName, spwd);
1717 
1718     // use default value for maximum password age to check that password
1719     // expiration was not specified
1720     // TODO: this default value might be changed, so it should be obtain
1721     // properly instead of hardcoding
1722     if (spwd.sp_max == 99999)
1723     {
1724         return getDefaultPasswordExpiration();
1725     }
1726 
1727     // process last change date and maximum password age according to
1728     // list_fields() in
1729     // https://github.com/shadow-maint/shadow/blob/7a796897e52293efe9e210ab8da32b7aefe65591/src/chage.c#L266
1730 
1731     // if last change is negative, then password does not exprire
1732     // if last change is positive and maximum password age is negative, then
1733     // password does not expire
1734     if (spwd.sp_lstchg < 0 || (spwd.sp_lstchg > 0 && spwd.sp_max < 0))
1735     {
1736         return getUnexpiringPasswordTime();
1737     }
1738 
1739     // if last change is 0, then password must be changed
1740     // https://linux.die.net/man/5/shadow assume its now
1741     if (spwd.sp_lstchg == 0)
1742     {
1743         using namespace std::chrono;
1744         return duration_cast<seconds>(system_clock::now().time_since_epoch())
1745             .count();
1746     }
1747 
1748     return daysToSeconds(static_cast<uint64_t>(spwd.sp_lstchg) + spwd.sp_max);
1749 }
1750 
1751 void UserMgr::setPasswordExpiration(const std::string& userName,
1752                                     const uint64_t value)
1753 {
1754     setPasswordExpirationImpl(userName, value);
1755 
1756     lg2::info("User's '{USER_NAME}' password expiration updated successfully",
1757               "USER_NAME", userName.c_str());
1758 }
1759 
1760 void UserMgr::setPasswordExpirationImpl(const std::string& userName,
1761                                         const uint64_t value)
1762 {
1763     // All user management lock has to be based on /etc/shadow
1764     // TODO  phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1765     const bool resetPasswordExpiration = (value == getUnexpiringPasswordTime());
1766 
1767     struct spwd spwd{};
1768     getShadowData(userName, spwd);
1769 
1770     // process last change date according to list_fields() in
1771     // https://github.com/shadow-maint/shadow/blob/7a796897e52293efe9e210ab8da32b7aefe65591/src/chage.c#L266
1772 
1773     long int lastChangeDate = spwd.sp_lstchg;
1774     if (lastChangeDate <= 0 && !resetPasswordExpiration)
1775     {
1776         // if last change is 0, then password must be changed
1777         // https://linux.die.net/man/5/shadow make last change value valid,
1778         // update it to today
1779         // if last change is negative, then password does not expire, update it
1780         // to today as well
1781         using namespace std::chrono;
1782         lastChangeDate =
1783             duration_cast<days>(system_clock::now().time_since_epoch()).count();
1784     }
1785 
1786     long int passwordAgeDays = spwd.sp_max;
1787     if (resetPasswordExpiration)
1788     {
1789         // if password expiration must be reset, do it via last negative maximum
1790         // password age
1791         passwordAgeDays = getUnexpiringPasswordAge();
1792     }
1793     else
1794     {
1795         const uint64_t date = secondsToDays(value);
1796         const long int expirationDate =
1797             (date > std::numeric_limits<long int>::max())
1798                 ? std::numeric_limits<long int>::max()
1799                 : date;
1800 
1801         // if password expiration date is less than last change date, then this
1802         // leads to the situation when password age is negative, which in turn
1803         // is treated by system as password does not expire, hence treat such a
1804         // value of password expiration as invalid
1805         if (expirationDate < lastChangeDate)
1806         {
1807             lg2::error(
1808                 "Password expiration date specified is less than password last change date for user '{USER_NAME}'",
1809                 "USER_NAME", userName.c_str());
1810             elog<InvalidArgument>(
1811                 Argument::ARGUMENT_NAME("User's password expiration date"),
1812                 Argument::ARGUMENT_VALUE("less then last change date"));
1813         }
1814 
1815         // set password expiration via maximum password age
1816         passwordAgeDays = expirationDate - lastChangeDate;
1817     }
1818 
1819     try
1820     {
1821         executeUserPasswordExpiration(userName.c_str(), lastChangeDate,
1822                                       passwordAgeDays);
1823     }
1824     catch (const std::exception& e)
1825     {
1826         lg2::error("Unable to update user's '{USER_NAME}' password expiration",
1827                    "USER_NAME", userName.c_str());
1828         elog<InternalFailure>();
1829     }
1830 }
1831 
1832 void UserMgr::createUser2(std::string userName, UserCreateMap createProps)
1833 {
1834     createUserImpl(userName, createProps);
1835 
1836     auto passwordExpiration = getDefaultPasswordExpiration();
1837     if (createProps.contains(UserProperty::PasswordExpiration))
1838         passwordExpiration =
1839             std::get<uint64_t>(createProps[UserProperty::PasswordExpiration]);
1840 
1841     // maximum value (default value of password expiration) means not to set
1842     // password expiration
1843     if (passwordExpiration != getDefaultPasswordExpiration())
1844     {
1845         try
1846         {
1847             setPasswordExpirationImpl(userName, passwordExpiration);
1848         }
1849         catch (const sdbusplus::exception::generated_exception& e2)
1850         {
1851             // delete user created by createUserImpl
1852             deleteUserImpl(userName);
1853             throw;
1854         }
1855         catch (const std::exception& e2)
1856         {
1857             // delete user created by createUserImpl
1858             deleteUserImpl(userName);
1859             lg2::error(
1860                 "User's password expiration value is incorrect for user '{USER_NAME}'",
1861                 "USER_NAME", userName.c_str());
1862 
1863             elog<InvalidArgument>(
1864                 Argument::ARGUMENT_NAME("Password Expiration"),
1865                 Argument::ARGUMENT_VALUE(
1866                     std::to_string(passwordExpiration).c_str()));
1867         }
1868     }
1869 
1870     lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
1871 }
1872 
1873 } // namespace user
1874 } // namespace phosphor
1875