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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 **/ 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 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 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 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 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 998 size_t UserMgr::getIpmiUsersCount() 999 { 1000 std::vector<std::string> userList = getUsersInGroup("ipmi"); 1001 return userList.size(); 1002 } 1003 1004 size_t UserMgr::getNonIpmiUsersCount() 1005 { 1006 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi"); 1007 return usersList.size() - ipmiUsers.size(); 1008 } 1009 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 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 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 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 1124 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 1151 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 1215 void UserMgr::executeGroupCreation(const char* groupName) 1216 { 1217 executeCmd("/usr/sbin/groupadd", groupName); 1218 } 1219 1220 void UserMgr::executeGroupDeletion(const char* groupName) 1221 { 1222 executeCmd("/usr/sbin/groupdel", groupName); 1223 } 1224 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 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 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 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 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 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 1552 void UserMgr::executeUserDelete(const char* userName) 1553 { 1554 executeCmd("/usr/sbin/userdel", userName, "-r"); 1555 } 1556 1557 void UserMgr::executeUserClearFailRecords(const char* userName) 1558 { 1559 executeCmd("/usr/sbin/faillock", "--user", userName, "--reset"); 1560 } 1561 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 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 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 1585 std::vector<std::string> UserMgr::getFailedAttempt(const char* userName) 1586 { 1587 return executeCmd("/usr/sbin/faillock", "--user", userName); 1588 } 1589 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 } 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