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