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