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