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