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