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