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
throwForMaxGrpUserCount(const std::vector<std::string> & groupNames)264 void UserMgr::throwForMaxGrpUserCount(
265 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 {};
889 struct spwd* spwdPtr = nullptr;
890 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
891 if (buflen <= 0)
892 {
893 // Use a default size if there is no hard limit suggested by sysconf()
894 buflen = 1024;
895 }
896 std::vector<char> buffer(buflen);
897 auto status =
898 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
899 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
900 // If no matching password record was found, these functions return 0
901 // and store NULL in *spwdPtr
902 if ((status == 0) && (&spwd == spwdPtr))
903 {
904 // Determine password validity per "chage" docs, where:
905 // spwd.sp_lstchg == 0 means password is expired, and
906 // spwd.sp_max == -1 means the password does not expire.
907 constexpr long secondsPerDay = 60 * 60 * 24;
908 long today = static_cast<long>(time(NULL)) / secondsPerDay;
909 if ((spwd.sp_lstchg == 0) ||
910 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
911 {
912 return true;
913 }
914 }
915 else
916 {
917 // User entry is missing in /etc/shadow, indicating no SHA password.
918 // Treat this as new user without password entry in /etc/shadow
919 // TODO: Add property to indicate user password was not set yet
920 // https://github.com/openbmc/phosphor-user-manager/issues/8
921 return false;
922 }
923
924 return false;
925 }
926
getUserAndSshGrpList()927 UserSSHLists UserMgr::getUserAndSshGrpList()
928 {
929 // All user management lock has to be based on /etc/shadow
930 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
931
932 std::vector<std::string> userList;
933 std::vector<std::string> sshUsersList;
934 struct passwd pw, *pwp = nullptr;
935 std::array<char, 1024> buffer{};
936
937 phosphor::user::File passwd(passwdFileName, "r");
938 if ((passwd)() == NULL)
939 {
940 lg2::error("Error opening {FILENAME}", "FILENAME", passwdFileName);
941 elog<InternalFailure>();
942 }
943
944 while (true)
945 {
946 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
947 &pwp);
948 if ((r != 0) || (pwp == NULL))
949 {
950 // Any error, break the loop.
951 break;
952 }
953 #ifdef ENABLE_ROOT_USER_MGMT
954 // Add all users whose UID >= 1000 and < 65534
955 // and special UID 0.
956 if ((pwp->pw_uid == 0) ||
957 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
958 #else
959 // Add all users whose UID >=1000 and < 65534
960 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
961 #endif
962 {
963 std::string userName(pwp->pw_name);
964 userList.emplace_back(userName);
965
966 // ssh doesn't have separate group. Check login shell entry to
967 // get all users list which are member of ssh group.
968 std::string loginShell(pwp->pw_shell);
969 if (loginShell == "/bin/sh")
970 {
971 sshUsersList.emplace_back(userName);
972 }
973 }
974 }
975 endpwent();
976 return std::make_pair(std::move(userList), std::move(sshUsersList));
977 }
978
getIpmiUsersCount()979 size_t UserMgr::getIpmiUsersCount()
980 {
981 std::vector<std::string> userList = getUsersInGroup("ipmi");
982 return userList.size();
983 }
984
getNonIpmiUsersCount()985 size_t UserMgr::getNonIpmiUsersCount()
986 {
987 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
988 return usersList.size() - ipmiUsers.size();
989 }
990
isUserEnabled(const std::string & userName)991 bool UserMgr::isUserEnabled(const std::string& userName)
992 {
993 // All user management lock has to be based on /etc/shadow
994 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
995 std::array<char, 4096> buffer{};
996 struct spwd spwd;
997 struct spwd* resultPtr = nullptr;
998 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
999 buffer.max_size(), &resultPtr);
1000 if (!status && (&spwd == resultPtr))
1001 {
1002 if (resultPtr->sp_expire >= 0)
1003 {
1004 return false; // user locked out
1005 }
1006 return true;
1007 }
1008 return false; // assume user is disabled for any error.
1009 }
1010
getUsersInGroup(const std::string & groupName)1011 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
1012 {
1013 std::vector<std::string> usersInGroup;
1014 // Should be more than enough to get the pwd structure.
1015 std::array<char, 4096> buffer{};
1016 struct group grp;
1017 struct group* resultPtr = nullptr;
1018
1019 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1020 buffer.max_size(), &resultPtr);
1021
1022 if (!status && (&grp == resultPtr))
1023 {
1024 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
1025 {
1026 usersInGroup.emplace_back(*(grp.gr_mem));
1027 }
1028 }
1029 else
1030 {
1031 lg2::error("Group '{GROUPNAME}' not found", "GROUPNAME", groupName);
1032 // Don't throw error, just return empty userList - fallback
1033 }
1034 return usersInGroup;
1035 }
1036
getPrivilegeMapperObject(void)1037 DbusUserObj UserMgr::getPrivilegeMapperObject(void)
1038 {
1039 DbusUserObj objects;
1040 try
1041 {
1042 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
1043 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
1044
1045 auto ldapMgmtService =
1046 getServiceName(std::move(basePath), std::move(interface));
1047 auto method = bus.new_method_call(
1048 ldapMgmtService.c_str(), ldapMgrObjBasePath,
1049 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1050
1051 auto reply = bus.call(method);
1052 reply.read(objects);
1053 }
1054 catch (const InternalFailure& e)
1055 {
1056 lg2::error("Unable to get the User Service: {ERR}", "ERR", e);
1057 throw;
1058 }
1059 catch (const sdbusplus::exception_t& e)
1060 {
1061 lg2::error("Failed to execute GetManagedObjects at {PATH}: {ERR}",
1062 "PATH", ldapMgrObjBasePath, "ERR", e);
1063 throw;
1064 }
1065 return objects;
1066 }
1067
getServiceName(std::string && path,std::string && intf)1068 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
1069 {
1070 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1071 objMapperInterface, "GetObject");
1072
1073 mapperCall.append(std::move(path));
1074 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1075
1076 auto mapperResponseMsg = bus.call(mapperCall);
1077
1078 if (mapperResponseMsg.is_method_error())
1079 {
1080 lg2::error("Error in mapper call");
1081 elog<InternalFailure>();
1082 }
1083
1084 std::map<std::string, std::vector<std::string>> mapperResponse;
1085 mapperResponseMsg.read(mapperResponse);
1086
1087 if (mapperResponse.begin() == mapperResponse.end())
1088 {
1089 lg2::error("Invalid response from mapper");
1090 elog<InternalFailure>();
1091 }
1092
1093 return mapperResponse.begin()->first;
1094 }
1095
getPrimaryGroup(const std::string & userName) const1096 gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
1097 {
1098 static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1099 if (buflen <= 0)
1100 {
1101 // Use a default size if there is no hard limit suggested by sysconf()
1102 buflen = 1024;
1103 }
1104
1105 struct passwd pwd;
1106 struct passwd* pwdPtr = nullptr;
1107 std::vector<char> buffer(buflen);
1108
1109 auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(),
1110 buffer.size(), &pwdPtr);
1111 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
1112 // If no matching password record was found, these functions return 0
1113 // and store NULL in *pwdPtr
1114 if (!status && (&pwd == pwdPtr))
1115 {
1116 return pwd.pw_gid;
1117 }
1118
1119 lg2::error("User {USERNAME} does not exist", "USERNAME", userName);
1120 elog<UserNameDoesNotExist>();
1121 }
1122
isGroupMember(const std::string & userName,gid_t primaryGid,const std::string & groupName) const1123 bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
1124 const std::string& groupName) const
1125 {
1126 static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1127 if (buflen <= 0)
1128 {
1129 // Use a default size if there is no hard limit suggested by sysconf()
1130 buflen = 1024;
1131 }
1132
1133 struct group grp;
1134 struct group* grpPtr = nullptr;
1135 std::vector<char> buffer(buflen);
1136
1137 auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1138 buffer.size(), &grpPtr);
1139
1140 // Groups with a lot of members may require a buffer of bigger size than
1141 // suggested by _SC_GETGR_R_SIZE_MAX.
1142 // 32K should be enough for about 2K members.
1143 constexpr auto maxBufferLength = 32 * 1024;
1144 while (status == ERANGE && buflen < maxBufferLength)
1145 {
1146 buflen *= 2;
1147 buffer.resize(buflen);
1148
1149 lg2::debug("Increase buffer for getgrnam_r() to {SIZE}", "SIZE",
1150 buflen);
1151
1152 status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1153 buffer.size(), &grpPtr);
1154 }
1155
1156 // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
1157 // If no matching group record was found, these functions return 0
1158 // and store NULL in *grpPtr
1159 if (!status && (&grp == grpPtr))
1160 {
1161 if (primaryGid == grp.gr_gid)
1162 {
1163 return true;
1164 }
1165
1166 for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
1167 {
1168 if (userName == grp.gr_mem[i])
1169 {
1170 return true;
1171 }
1172 }
1173 }
1174 else if (status == ERANGE)
1175 {
1176 lg2::error("Group info of {GROUP} requires too much memory", "GROUP",
1177 groupName);
1178 }
1179 else
1180 {
1181 lg2::error("Group {GROUP} does not exist", "GROUP", groupName);
1182 }
1183
1184 return false;
1185 }
1186
executeGroupCreation(const char * groupName)1187 void UserMgr::executeGroupCreation(const char* groupName)
1188 {
1189 executeCmd("/usr/sbin/groupadd", groupName);
1190 }
1191
executeGroupDeletion(const char * groupName)1192 void UserMgr::executeGroupDeletion(const char* groupName)
1193 {
1194 executeCmd("/usr/sbin/groupdel", groupName);
1195 }
1196
getUserInfo(std::string userName)1197 UserInfoMap UserMgr::getUserInfo(std::string userName)
1198 {
1199 UserInfoMap userInfo;
1200 // Check whether the given user is local user or not.
1201 if (isUserExist(userName))
1202 {
1203 const auto& user = usersList[userName];
1204 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1205 userInfo.emplace("UserGroups", user.get()->userGroups());
1206 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1207 userInfo.emplace("UserLockedForFailedAttempt",
1208 user.get()->userLockedForFailedAttempt());
1209 userInfo.emplace("UserPasswordExpired",
1210 user.get()->userPasswordExpired());
1211 userInfo.emplace("RemoteUser", false);
1212 }
1213 else
1214 {
1215 auto primaryGid = getPrimaryGroup(userName);
1216
1217 DbusUserObj objects = getPrivilegeMapperObject();
1218
1219 std::string ldapConfigPath;
1220 std::string userPrivilege;
1221
1222 try
1223 {
1224 for (const auto& [path, interfaces] : objects)
1225 {
1226 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
1227 if (it != interfaces.end())
1228 {
1229 auto propIt = it->second.find("Enabled");
1230 if (propIt != it->second.end() &&
1231 std::get<bool>(propIt->second))
1232 {
1233 ldapConfigPath = path.str + '/';
1234 break;
1235 }
1236 }
1237 }
1238
1239 if (ldapConfigPath.empty())
1240 {
1241 return userInfo;
1242 }
1243
1244 for (const auto& [path, interfaces] : objects)
1245 {
1246 if (!path.str.starts_with(ldapConfigPath))
1247 {
1248 continue;
1249 }
1250
1251 auto it = interfaces.find(
1252 "xyz.openbmc_project.User.PrivilegeMapperEntry");
1253 if (it != interfaces.end())
1254 {
1255 std::string privilege;
1256 std::string groupName;
1257
1258 for (const auto& [propName, propValue] : it->second)
1259 {
1260 if (propName == "GroupName")
1261 {
1262 groupName = std::get<std::string>(propValue);
1263 }
1264 else if (propName == "Privilege")
1265 {
1266 privilege = std::get<std::string>(propValue);
1267 }
1268 }
1269
1270 if (!groupName.empty() && !privilege.empty() &&
1271 isGroupMember(userName, primaryGid, groupName))
1272 {
1273 userPrivilege = privilege;
1274 break;
1275 }
1276 }
1277 if (!userPrivilege.empty())
1278 {
1279 break;
1280 }
1281 }
1282
1283 if (!userPrivilege.empty())
1284 {
1285 userInfo.emplace("UserPrivilege", userPrivilege);
1286 }
1287 else
1288 {
1289 lg2::warning("LDAP group privilege mapping does not exist, "
1290 "default \"priv-user\" is used");
1291 userInfo.emplace("UserPrivilege", "priv-user");
1292 }
1293 }
1294 catch (const std::bad_variant_access& e)
1295 {
1296 lg2::error("Error while accessing variant: {ERR}", "ERR", e);
1297 elog<InternalFailure>();
1298 }
1299 userInfo.emplace("RemoteUser", true);
1300 }
1301
1302 return userInfo;
1303 }
1304
initializeAccountPolicy()1305 void UserMgr::initializeAccountPolicy()
1306 {
1307 std::string valueStr;
1308 auto value = minPasswdLength;
1309 unsigned long tmp = 0;
1310 if (getPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
1311 valueStr) != success)
1312 {
1313 AccountPolicyIface::minPasswordLength(minPasswdLength);
1314 }
1315 else
1316 {
1317 try
1318 {
1319 tmp = std::stoul(valueStr, nullptr);
1320 if (tmp > std::numeric_limits<decltype(value)>::max())
1321 {
1322 throw std::out_of_range("Out of range");
1323 }
1324 value = static_cast<decltype(value)>(tmp);
1325 }
1326 catch (const std::exception& e)
1327 {
1328 lg2::error("Exception for MinPasswordLength: {ERR}", "ERR", e);
1329 throw;
1330 }
1331 AccountPolicyIface::minPasswordLength(value);
1332 }
1333 valueStr.clear();
1334 if (getPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
1335 valueStr) != success)
1336 {
1337 AccountPolicyIface::rememberOldPasswordTimes(0);
1338 }
1339 else
1340 {
1341 value = 0;
1342 try
1343 {
1344 tmp = std::stoul(valueStr, nullptr);
1345 if (tmp > std::numeric_limits<decltype(value)>::max())
1346 {
1347 throw std::out_of_range("Out of range");
1348 }
1349 value = static_cast<decltype(value)>(tmp);
1350 }
1351 catch (const std::exception& e)
1352 {
1353 lg2::error("Exception for RememberOldPasswordTimes: {ERR}", "ERR",
1354 e);
1355 throw;
1356 }
1357 AccountPolicyIface::rememberOldPasswordTimes(value);
1358 }
1359 valueStr.clear();
1360 if (getPamModuleConfValue(faillockConfigFile, maxFailedAttempt, valueStr) !=
1361 success)
1362 {
1363 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1364 }
1365 else
1366 {
1367 uint16_t value16 = 0;
1368 try
1369 {
1370 tmp = std::stoul(valueStr, nullptr);
1371 if (tmp > std::numeric_limits<decltype(value16)>::max())
1372 {
1373 throw std::out_of_range("Out of range");
1374 }
1375 value16 = static_cast<decltype(value16)>(tmp);
1376 }
1377 catch (const std::exception& e)
1378 {
1379 lg2::error("Exception for MaxLoginAttemptBeforLockout: {ERR}",
1380 "ERR", e);
1381 throw;
1382 }
1383 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1384 }
1385 valueStr.clear();
1386 if (getPamModuleConfValue(faillockConfigFile, unlockTimeout, valueStr) !=
1387 success)
1388 {
1389 AccountPolicyIface::accountUnlockTimeout(0);
1390 }
1391 else
1392 {
1393 uint32_t value32 = 0;
1394 try
1395 {
1396 tmp = std::stoul(valueStr, nullptr);
1397 if (tmp > std::numeric_limits<decltype(value32)>::max())
1398 {
1399 throw std::out_of_range("Out of range");
1400 }
1401 value32 = static_cast<decltype(value32)>(tmp);
1402 }
1403 catch (const std::exception& e)
1404 {
1405 lg2::error("Exception for AccountUnlockTimeout: {ERR}", "ERR", e);
1406 throw;
1407 }
1408 AccountPolicyIface::accountUnlockTimeout(value32);
1409 }
1410 }
1411
initUserObjects(void)1412 void UserMgr::initUserObjects(void)
1413 {
1414 // All user management lock has to be based on /etc/shadow
1415 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1416 std::vector<std::string> userNameList;
1417 std::vector<std::string> sshGrpUsersList;
1418 UserSSHLists userSSHLists = getUserAndSshGrpList();
1419 userNameList = std::move(userSSHLists.first);
1420 sshGrpUsersList = std::move(userSSHLists.second);
1421
1422 if (!userNameList.empty())
1423 {
1424 std::map<std::string, std::vector<std::string>> groupLists;
1425 // We only track users that are in the |predefinedGroups|
1426 // The other groups don't contain real BMC users.
1427 for (const char* grp : predefinedGroups)
1428 {
1429 if (grp == grpSsh)
1430 {
1431 groupLists.emplace(grp, sshGrpUsersList);
1432 }
1433 else
1434 {
1435 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1436 groupLists.emplace(grp, grpUsersList);
1437 }
1438 }
1439 for (auto& grp : privMgr)
1440 {
1441 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1442 groupLists.emplace(grp, grpUsersList);
1443 }
1444
1445 for (auto& user : userNameList)
1446 {
1447 std::vector<std::string> userGroups;
1448 std::string userPriv;
1449 for (const auto& grp : groupLists)
1450 {
1451 std::vector<std::string> tempGrp = grp.second;
1452 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1453 tempGrp.end())
1454 {
1455 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1456 privMgr.end())
1457 {
1458 userPriv = grp.first;
1459 }
1460 else
1461 {
1462 userGroups.emplace_back(grp.first);
1463 }
1464 }
1465 }
1466 // Add user objects to the Users path.
1467 sdbusplus::message::object_path tempObjPath(usersObjPath);
1468 tempObjPath /= user;
1469 std::string objPath(tempObjPath);
1470 std::sort(userGroups.begin(), userGroups.end());
1471 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1472 bus, objPath.c_str(), userGroups,
1473 userPriv, isUserEnabled(user), *this));
1474 }
1475 }
1476 }
1477
UserMgr(sdbusplus::bus_t & bus,const char * path)1478 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1479 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1480 faillockConfigFile(defaultFaillockConfigFile),
1481 pwHistoryConfigFile(defaultPWHistoryConfigFile),
1482 pwQualityConfigFile(defaultPWQualityConfigFile)
1483 {
1484 UserMgrIface::allPrivileges(privMgr);
1485 groupsMgr = readAllGroupsOnSystem();
1486 std::sort(groupsMgr.begin(), groupsMgr.end());
1487 UserMgrIface::allGroups(groupsMgr);
1488 initializeAccountPolicy();
1489 initUserObjects();
1490
1491 // emit the signal
1492 this->emit_object_added();
1493 }
1494
executeUserAdd(const char * userName,const char * groups,bool sshRequested,bool enabled)1495 void UserMgr::executeUserAdd(const char* userName, const char* groups,
1496 bool sshRequested, bool enabled)
1497 {
1498 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1499 // 1970-01-01, that's an implementation-defined behavior
1500 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
1501 (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
1502 (enabled ? "" : "1970-01-01"));
1503 }
1504
executeUserDelete(const char * userName)1505 void UserMgr::executeUserDelete(const char* userName)
1506 {
1507 executeCmd("/usr/sbin/userdel", userName, "-r");
1508 }
1509
executeUserClearFailRecords(const char * userName)1510 void UserMgr::executeUserClearFailRecords(const char* userName)
1511 {
1512 executeCmd("/usr/sbin/faillock", "--user", userName, "--reset");
1513 }
1514
executeUserRename(const char * userName,const char * newUserName)1515 void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1516 {
1517 std::string newHomeDir = "/home/";
1518 newHomeDir += newUserName;
1519 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1520 newHomeDir.c_str(), "-m");
1521 }
1522
executeUserModify(const char * userName,const char * newGroups,bool sshRequested)1523 void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1524 bool sshRequested)
1525 {
1526 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
1527 (sshRequested ? "/bin/sh" : "/sbin/nologin"));
1528 }
1529
executeUserModifyUserEnable(const char * userName,bool enabled)1530 void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1531 {
1532 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1533 // 1970-01-01, that's an implementation-defined behavior
1534 executeCmd("/usr/sbin/usermod", userName, "-e",
1535 (enabled ? "" : "1970-01-01"));
1536 }
1537
getFailedAttempt(const char * userName)1538 std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1539 {
1540 return executeCmd("/usr/sbin/faillock", "--user", userName);
1541 }
1542
1543 } // namespace user
1544 } // namespace phosphor
1545