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 // 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) 593 { 594 lg2::error("Attempting to set minPasswordLength to {VALUE}, less than " 595 "{MINVALUE}", 596 "VALUE", value, "MINVALUE", minPasswdLength); 597 elog<InvalidArgument>( 598 Argument::ARGUMENT_NAME("minPasswordLength"), 599 Argument::ARGUMENT_VALUE(std::to_string(value).c_str())); 600 } 601 if (setPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp, 602 std::to_string(value)) != success) 603 { 604 lg2::error("Unable to set minPasswordLength to {VALUE}", "VALUE", 605 value); 606 elog<InternalFailure>(); 607 } 608 return AccountPolicyIface::minPasswordLength(value); 609 } 610 611 uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value) 612 { 613 if (value == AccountPolicyIface::rememberOldPasswordTimes()) 614 { 615 return value; 616 } 617 if (setPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount, 618 std::to_string(value)) != success) 619 { 620 lg2::error("Unable to set rememberOldPasswordTimes to {VALUE}", "VALUE", 621 value); 622 elog<InternalFailure>(); 623 } 624 return AccountPolicyIface::rememberOldPasswordTimes(value); 625 } 626 627 uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value) 628 { 629 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout()) 630 { 631 return value; 632 } 633 if (setPamModuleConfValue(faillockConfigFile, maxFailedAttempt, 634 std::to_string(value)) != success) 635 { 636 lg2::error("Unable to set maxLoginAttemptBeforeLockout to {VALUE}", 637 "VALUE", value); 638 elog<InternalFailure>(); 639 } 640 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value); 641 } 642 643 uint32_t UserMgr::accountUnlockTimeout(uint32_t value) 644 { 645 if (value == AccountPolicyIface::accountUnlockTimeout()) 646 { 647 return value; 648 } 649 if (setPamModuleConfValue(faillockConfigFile, unlockTimeout, 650 std::to_string(value)) != success) 651 { 652 lg2::error("Unable to set accountUnlockTimeout to {VALUE}", "VALUE", 653 value); 654 elog<InternalFailure>(); 655 } 656 return AccountPolicyIface::accountUnlockTimeout(value); 657 } 658 659 int UserMgr::getPamModuleConfValue(const std::string& confFile, 660 const std::string& argName, 661 std::string& argValue) 662 { 663 std::ifstream fileToRead(confFile, std::ios::in); 664 if (!fileToRead.is_open()) 665 { 666 lg2::error("Failed to open pam configuration file {FILENAME}", 667 "FILENAME", confFile); 668 return failure; 669 } 670 std::string line; 671 auto argSearch = argName + "="; 672 size_t startPos = 0; 673 size_t endPos = 0; 674 while (getline(fileToRead, line)) 675 { 676 // skip comments section starting with # 677 if ((startPos = line.find('#')) != std::string::npos) 678 { 679 if (startPos == 0) 680 { 681 continue; 682 } 683 // skip comments after meaningful section and process those 684 line = line.substr(0, startPos); 685 } 686 if ((startPos = line.find(argSearch)) != std::string::npos) 687 { 688 if ((endPos = line.find(' ', startPos)) == std::string::npos) 689 { 690 endPos = line.size(); 691 } 692 startPos += argSearch.size(); 693 argValue = line.substr(startPos, endPos - startPos); 694 return success; 695 } 696 } 697 return failure; 698 } 699 700 int UserMgr::setPamModuleConfValue(const std::string& confFile, 701 const std::string& argName, 702 const std::string& argValue) 703 { 704 std::string tmpConfFile = confFile + "_tmp"; 705 std::ifstream fileToRead(confFile, std::ios::in); 706 std::ofstream fileToWrite(tmpConfFile, std::ios::out); 707 if (!fileToRead.is_open() || !fileToWrite.is_open()) 708 { 709 lg2::error("Failed to open pam configuration file {FILENAME}", 710 "FILENAME", confFile); 711 // Delete the unused tmp file 712 std::remove(tmpConfFile.c_str()); 713 return failure; 714 } 715 std::string line; 716 auto argSearch = argName + "="; 717 size_t startPos = 0; 718 size_t endPos = 0; 719 bool found = false; 720 while (getline(fileToRead, line)) 721 { 722 // skip comments section starting with # 723 if ((startPos = line.find('#')) != std::string::npos) 724 { 725 if (startPos == 0) 726 { 727 fileToWrite << line << std::endl; 728 continue; 729 } 730 // skip comments after meaningful section and process those 731 line = line.substr(0, startPos); 732 } 733 if ((startPos = line.find(argSearch)) != std::string::npos) 734 { 735 if ((endPos = line.find(' ', startPos)) == std::string::npos) 736 { 737 endPos = line.size(); 738 } 739 startPos += argSearch.size(); 740 fileToWrite << line.substr(0, startPos) << argValue 741 << line.substr(endPos, line.size() - endPos) 742 << std::endl; 743 found = true; 744 continue; 745 } 746 fileToWrite << line << std::endl; 747 } 748 fileToWrite.close(); 749 fileToRead.close(); 750 if (found) 751 { 752 if (std::rename(tmpConfFile.c_str(), confFile.c_str()) == 0) 753 { 754 return success; 755 } 756 } 757 // No changes, so delete the unused tmp file 758 std::remove(tmpConfFile.c_str()); 759 return failure; 760 } 761 762 void UserMgr::userEnable(const std::string& userName, bool enabled) 763 { 764 // All user management lock has to be based on /etc/shadow 765 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 766 throwForUserDoesNotExist(userName); 767 try 768 { 769 executeUserModifyUserEnable(userName.c_str(), enabled); 770 } 771 catch (const InternalFailure& e) 772 { 773 lg2::error("Unable to modify user enabled state for '{USERNAME}'", 774 "USERNAME", userName); 775 elog<InternalFailure>(); 776 } 777 778 usersList[userName]->setUserEnabled(enabled); 779 lg2::info("User '{USERNAME}' has been {STATUS}", "USERNAME", userName, 780 "STATUS", enabled ? "Enabled" : "Disabled"); 781 } 782 783 /** 784 * faillock app will provide the user failed login list with when the attempt 785 * was made, the type, the source, and if it's valid. 786 * 787 * Valid in this case means that the attempt was made within the fail_interval 788 * time. So, we can check this list for the number of valid entries (lines 789 * ending with 'V') compared to the maximum allowed to determine if the user is 790 * locked out. 791 * 792 * This data is only refreshed when an attempt is made, so if the user appears 793 * to be locked out, we must also check if the most recent attempt was older 794 * than the unlock_time to know if the user has since been unlocked. 795 **/ 796 bool UserMgr::parseFaillockForLockout( 797 const std::vector<std::string>& faillockOutput) 798 { 799 uint16_t failAttempts = 0; 800 time_t lastFailedAttempt{}; 801 for (const std::string& line : faillockOutput) 802 { 803 if (!line.ends_with("V")) 804 { 805 continue; 806 } 807 808 // Count this failed attempt 809 failAttempts++; 810 811 // Update the last attempt time 812 // First get the "when" which is the first two words (date and time) 813 size_t pos = line.find(" "); 814 if (pos == std::string::npos) 815 { 816 continue; 817 } 818 pos = line.find(" ", pos + 1); 819 if (pos == std::string::npos) 820 { 821 continue; 822 } 823 std::string failDateTime = line.substr(0, pos); 824 825 // NOTE: Cannot use std::get_time() here as the implementation of %y in 826 // libstdc++ does not match POSIX strptime() before gcc 12.1.0 827 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888 828 std::tm tmStruct = {}; 829 if (!strptime(failDateTime.c_str(), "%F %T", &tmStruct)) 830 { 831 lg2::error("Failed to parse latest failure date/time"); 832 elog<InternalFailure>(); 833 } 834 835 time_t failTimestamp = std::mktime(&tmStruct); 836 lastFailedAttempt = std::max(failTimestamp, lastFailedAttempt); 837 } 838 839 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout()) 840 { 841 return false; 842 } 843 844 if (lastFailedAttempt + 845 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <= 846 std::time(NULL)) 847 { 848 return false; 849 } 850 851 return true; 852 } 853 854 bool UserMgr::userLockedForFailedAttempt(const std::string& userName) 855 { 856 // All user management lock has to be based on /etc/shadow 857 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 858 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0) 859 { 860 return false; 861 } 862 863 std::vector<std::string> output; 864 try 865 { 866 output = getFailedAttempt(userName.c_str()); 867 } 868 catch (const InternalFailure& e) 869 { 870 lg2::error("Unable to read login failure counter"); 871 elog<InternalFailure>(); 872 } 873 874 return parseFaillockForLockout(output); 875 } 876 877 bool UserMgr::userLockedForFailedAttempt(const std::string& userName, 878 const bool& value) 879 { 880 // All user management lock has to be based on /etc/shadow 881 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 882 if (value == true) 883 { 884 return userLockedForFailedAttempt(userName); 885 } 886 887 try 888 { 889 // Clear user fail records 890 executeUserClearFailRecords(userName.c_str()); 891 } 892 catch (const InternalFailure& e) 893 { 894 lg2::error("Unable to reset login failure counter"); 895 elog<InternalFailure>(); 896 } 897 898 return userLockedForFailedAttempt(userName); 899 } 900 901 bool UserMgr::userPasswordExpired(const std::string& userName) 902 { 903 // All user management lock has to be based on /etc/shadow 904 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 905 906 struct spwd spwd{}; 907 struct spwd* spwdPtr = nullptr; 908 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); 909 if (buflen <= 0) 910 { 911 // Use a default size if there is no hard limit suggested by sysconf() 912 buflen = 1024; 913 } 914 std::vector<char> buffer(buflen); 915 auto status = 916 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr); 917 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd. 918 // If no matching password record was found, these functions return 0 919 // and store NULL in *spwdPtr 920 if ((status == 0) && (&spwd == spwdPtr)) 921 { 922 // Determine password validity per "chage" docs, where: 923 // spwd.sp_lstchg == 0 means password is expired, and 924 // spwd.sp_max == -1 means the password does not expire. 925 constexpr long secondsPerDay = 60 * 60 * 24; 926 long today = static_cast<long>(time(NULL)) / secondsPerDay; 927 if ((spwd.sp_lstchg == 0) || 928 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today))) 929 { 930 return true; 931 } 932 } 933 else 934 { 935 // User entry is missing in /etc/shadow, indicating no SHA password. 936 // Treat this as new user without password entry in /etc/shadow 937 // TODO: Add property to indicate user password was not set yet 938 // https://github.com/openbmc/phosphor-user-manager/issues/8 939 return false; 940 } 941 942 return false; 943 } 944 945 UserSSHLists UserMgr::getUserAndSshGrpList() 946 { 947 // All user management lock has to be based on /etc/shadow 948 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 949 950 std::vector<std::string> userList; 951 std::vector<std::string> sshUsersList; 952 struct passwd pw, *pwp = nullptr; 953 std::array<char, 1024> buffer{}; 954 955 phosphor::user::File passwd(passwdFileName, "r"); 956 if ((passwd)() == NULL) 957 { 958 lg2::error("Error opening {FILENAME}", "FILENAME", passwdFileName); 959 elog<InternalFailure>(); 960 } 961 962 while (true) 963 { 964 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(), 965 &pwp); 966 if ((r != 0) || (pwp == NULL)) 967 { 968 // Any error, break the loop. 969 break; 970 } 971 #ifdef ENABLE_ROOT_USER_MGMT 972 // Add all users whose UID >= 1000 and < 65534 973 // and special UID 0. 974 if ((pwp->pw_uid == 0) || 975 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))) 976 #else 977 // Add all users whose UID >=1000 and < 65534 978 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)) 979 #endif 980 { 981 std::string userName(pwp->pw_name); 982 userList.emplace_back(userName); 983 984 // ssh doesn't have separate group. Check login shell entry to 985 // get all users list which are member of ssh group. 986 std::string loginShell(pwp->pw_shell); 987 if (loginShell == "/bin/sh") 988 { 989 sshUsersList.emplace_back(userName); 990 } 991 } 992 } 993 endpwent(); 994 return std::make_pair(std::move(userList), std::move(sshUsersList)); 995 } 996 997 size_t UserMgr::getIpmiUsersCount() 998 { 999 std::vector<std::string> userList = getUsersInGroup("ipmi"); 1000 return userList.size(); 1001 } 1002 1003 size_t UserMgr::getNonIpmiUsersCount() 1004 { 1005 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi"); 1006 return usersList.size() - ipmiUsers.size(); 1007 } 1008 1009 bool UserMgr::isUserEnabled(const std::string& userName) 1010 { 1011 // All user management lock has to be based on /etc/shadow 1012 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 1013 std::array<char, 4096> buffer{}; 1014 struct spwd spwd; 1015 struct spwd* resultPtr = nullptr; 1016 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(), 1017 buffer.max_size(), &resultPtr); 1018 if (!status && (&spwd == resultPtr)) 1019 { 1020 // according to chage/usermod code -1 means that account does not expire 1021 // https://github.com/shadow-maint/shadow/blob/7a796897e52293efe9e210ab8da32b7aefe65591/src/chage.c 1022 if (resultPtr->sp_expire < 0) 1023 { 1024 return true; 1025 } 1026 1027 // check account expiration date against current date 1028 if (resultPtr->sp_expire > currentDate()) 1029 { 1030 return true; 1031 } 1032 1033 return false; 1034 } 1035 return false; // assume user is disabled for any error. 1036 } 1037 1038 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName) 1039 { 1040 std::vector<std::string> usersInGroup; 1041 // Should be more than enough to get the pwd structure. 1042 std::array<char, 4096> buffer{}; 1043 struct group grp; 1044 struct group* resultPtr = nullptr; 1045 1046 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(), 1047 buffer.max_size(), &resultPtr); 1048 1049 if (!status && (&grp == resultPtr)) 1050 { 1051 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem)) 1052 { 1053 usersInGroup.emplace_back(*(grp.gr_mem)); 1054 } 1055 } 1056 else 1057 { 1058 lg2::error("Group '{GROUPNAME}' not found", "GROUPNAME", groupName); 1059 // Don't throw error, just return empty userList - fallback 1060 } 1061 return usersInGroup; 1062 } 1063 1064 DbusUserObj UserMgr::getPrivilegeMapperObject(void) 1065 { 1066 DbusUserObj objects; 1067 try 1068 { 1069 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap"; 1070 std::string interface = "xyz.openbmc_project.User.Ldap.Config"; 1071 1072 auto ldapMgmtService = 1073 getServiceName(std::move(basePath), std::move(interface)); 1074 auto method = bus.new_method_call( 1075 ldapMgmtService.c_str(), ldapMgrObjBasePath, 1076 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1077 1078 auto reply = bus.call(method); 1079 reply.read(objects); 1080 } 1081 catch (const InternalFailure& e) 1082 { 1083 lg2::error("Unable to get the User Service: {ERR}", "ERR", e); 1084 throw; 1085 } 1086 catch (const sdbusplus::exception_t& e) 1087 { 1088 lg2::error("Failed to execute GetManagedObjects at {PATH}: {ERR}", 1089 "PATH", ldapMgrObjBasePath, "ERR", e); 1090 throw; 1091 } 1092 return objects; 1093 } 1094 1095 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf) 1096 { 1097 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath, 1098 objMapperInterface, "GetObject"); 1099 1100 mapperCall.append(std::move(path)); 1101 mapperCall.append(std::vector<std::string>({std::move(intf)})); 1102 1103 auto mapperResponseMsg = bus.call(mapperCall); 1104 1105 if (mapperResponseMsg.is_method_error()) 1106 { 1107 lg2::error("Error in mapper call"); 1108 elog<InternalFailure>(); 1109 } 1110 1111 std::map<std::string, std::vector<std::string>> mapperResponse; 1112 mapperResponseMsg.read(mapperResponse); 1113 1114 if (mapperResponse.begin() == mapperResponse.end()) 1115 { 1116 lg2::error("Invalid response from mapper"); 1117 elog<InternalFailure>(); 1118 } 1119 1120 return mapperResponse.begin()->first; 1121 } 1122 1123 gid_t UserMgr::getPrimaryGroup(const std::string& userName) const 1124 { 1125 static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); 1126 if (buflen <= 0) 1127 { 1128 // Use a default size if there is no hard limit suggested by sysconf() 1129 buflen = 1024; 1130 } 1131 1132 struct passwd pwd; 1133 struct passwd* pwdPtr = nullptr; 1134 std::vector<char> buffer(buflen); 1135 1136 auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(), 1137 buffer.size(), &pwdPtr); 1138 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd. 1139 // If no matching password record was found, these functions return 0 1140 // and store NULL in *pwdPtr 1141 if (!status && (&pwd == pwdPtr)) 1142 { 1143 return pwd.pw_gid; 1144 } 1145 1146 lg2::error("User {USERNAME} does not exist", "USERNAME", userName); 1147 elog<UserNameDoesNotExist>(); 1148 } 1149 1150 bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid, 1151 const std::string& groupName) const 1152 { 1153 static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX); 1154 if (buflen <= 0) 1155 { 1156 // Use a default size if there is no hard limit suggested by sysconf() 1157 buflen = 1024; 1158 } 1159 1160 struct group grp; 1161 struct group* grpPtr = nullptr; 1162 std::vector<char> buffer(buflen); 1163 1164 auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(), 1165 buffer.size(), &grpPtr); 1166 1167 // Groups with a lot of members may require a buffer of bigger size than 1168 // suggested by _SC_GETGR_R_SIZE_MAX. 1169 // 32K should be enough for about 2K members. 1170 constexpr auto maxBufferLength = 32 * 1024; 1171 while (status == ERANGE && buflen < maxBufferLength) 1172 { 1173 buflen *= 2; 1174 buffer.resize(buflen); 1175 1176 lg2::debug("Increase buffer for getgrnam_r() to {SIZE}", "SIZE", 1177 buflen); 1178 1179 status = getgrnam_r(groupName.c_str(), &grp, buffer.data(), 1180 buffer.size(), &grpPtr); 1181 } 1182 1183 // On success, getgrnam_r() returns zero, and set *grpPtr to grp. 1184 // If no matching group record was found, these functions return 0 1185 // and store NULL in *grpPtr 1186 if (!status && (&grp == grpPtr)) 1187 { 1188 if (primaryGid == grp.gr_gid) 1189 { 1190 return true; 1191 } 1192 1193 for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i) 1194 { 1195 if (userName == grp.gr_mem[i]) 1196 { 1197 return true; 1198 } 1199 } 1200 } 1201 else if (status == ERANGE) 1202 { 1203 lg2::error("Group info of {GROUP} requires too much memory", "GROUP", 1204 groupName); 1205 } 1206 else 1207 { 1208 lg2::error("Group {GROUP} does not exist", "GROUP", groupName); 1209 } 1210 1211 return false; 1212 } 1213 1214 void UserMgr::executeGroupCreation(const char* groupName) 1215 { 1216 executeCmd("/usr/sbin/groupadd", groupName); 1217 } 1218 1219 void UserMgr::executeGroupDeletion(const char* groupName) 1220 { 1221 executeCmd("/usr/sbin/groupdel", groupName); 1222 } 1223 1224 UserInfoMap UserMgr::getUserInfo(std::string userName) 1225 { 1226 UserInfoMap userInfo; 1227 // Check whether the given user is local user or not. 1228 if (isUserExist(userName)) 1229 { 1230 const auto& user = usersList[userName]; 1231 userInfo.emplace("UserPrivilege", user.get()->userPrivilege()); 1232 userInfo.emplace("UserGroups", user.get()->userGroups()); 1233 userInfo.emplace("UserEnabled", user.get()->userEnabled()); 1234 userInfo.emplace("UserLockedForFailedAttempt", 1235 user.get()->userLockedForFailedAttempt()); 1236 userInfo.emplace("UserPasswordExpired", 1237 user.get()->userPasswordExpired()); 1238 userInfo.emplace("TOTPSecretkeyRequired", 1239 user.get()->secretKeyGenerationRequired()); 1240 userInfo.emplace("RemoteUser", false); 1241 } 1242 else 1243 { 1244 auto primaryGid = getPrimaryGroup(userName); 1245 1246 DbusUserObj objects = getPrivilegeMapperObject(); 1247 1248 std::string ldapConfigPath; 1249 std::string userPrivilege; 1250 1251 try 1252 { 1253 for (const auto& [path, interfaces] : objects) 1254 { 1255 auto it = interfaces.find("xyz.openbmc_project.Object.Enable"); 1256 if (it != interfaces.end()) 1257 { 1258 auto propIt = it->second.find("Enabled"); 1259 if (propIt != it->second.end() && 1260 std::get<bool>(propIt->second)) 1261 { 1262 ldapConfigPath = path.str + '/'; 1263 break; 1264 } 1265 } 1266 } 1267 1268 if (ldapConfigPath.empty()) 1269 { 1270 return userInfo; 1271 } 1272 1273 for (const auto& [path, interfaces] : objects) 1274 { 1275 if (!path.str.starts_with(ldapConfigPath)) 1276 { 1277 continue; 1278 } 1279 1280 auto it = interfaces.find( 1281 "xyz.openbmc_project.User.PrivilegeMapperEntry"); 1282 if (it != interfaces.end()) 1283 { 1284 std::string privilege; 1285 std::string groupName; 1286 1287 for (const auto& [propName, propValue] : it->second) 1288 { 1289 if (propName == "GroupName") 1290 { 1291 groupName = std::get<std::string>(propValue); 1292 } 1293 else if (propName == "Privilege") 1294 { 1295 privilege = std::get<std::string>(propValue); 1296 } 1297 } 1298 1299 if (!groupName.empty() && !privilege.empty() && 1300 isGroupMember(userName, primaryGid, groupName)) 1301 { 1302 userPrivilege = privilege; 1303 break; 1304 } 1305 } 1306 if (!userPrivilege.empty()) 1307 { 1308 break; 1309 } 1310 } 1311 1312 if (!userPrivilege.empty()) 1313 { 1314 userInfo.emplace("UserPrivilege", userPrivilege); 1315 } 1316 else 1317 { 1318 lg2::warning("LDAP group privilege mapping does not exist, " 1319 "default \"priv-user\" is used"); 1320 userInfo.emplace("UserPrivilege", "priv-user"); 1321 } 1322 } 1323 catch (const std::bad_variant_access& e) 1324 { 1325 lg2::error("Error while accessing variant: {ERR}", "ERR", e); 1326 elog<InternalFailure>(); 1327 } 1328 userInfo.emplace("RemoteUser", true); 1329 } 1330 1331 return userInfo; 1332 } 1333 1334 void UserMgr::initializeAccountPolicy() 1335 { 1336 std::string valueStr; 1337 auto value = minPasswdLength; 1338 unsigned long tmp = 0; 1339 if (getPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp, 1340 valueStr) != success) 1341 { 1342 AccountPolicyIface::minPasswordLength(minPasswdLength); 1343 } 1344 else 1345 { 1346 try 1347 { 1348 tmp = std::stoul(valueStr, nullptr); 1349 if (tmp > std::numeric_limits<decltype(value)>::max()) 1350 { 1351 throw std::out_of_range("Out of range"); 1352 } 1353 value = static_cast<decltype(value)>(tmp); 1354 } 1355 catch (const std::exception& e) 1356 { 1357 lg2::error("Exception for MinPasswordLength: {ERR}", "ERR", e); 1358 throw; 1359 } 1360 AccountPolicyIface::minPasswordLength(value); 1361 } 1362 valueStr.clear(); 1363 if (getPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount, 1364 valueStr) != success) 1365 { 1366 AccountPolicyIface::rememberOldPasswordTimes(0); 1367 } 1368 else 1369 { 1370 value = 0; 1371 try 1372 { 1373 tmp = std::stoul(valueStr, nullptr); 1374 if (tmp > std::numeric_limits<decltype(value)>::max()) 1375 { 1376 throw std::out_of_range("Out of range"); 1377 } 1378 value = static_cast<decltype(value)>(tmp); 1379 } 1380 catch (const std::exception& e) 1381 { 1382 lg2::error("Exception for RememberOldPasswordTimes: {ERR}", "ERR", 1383 e); 1384 throw; 1385 } 1386 AccountPolicyIface::rememberOldPasswordTimes(value); 1387 } 1388 valueStr.clear(); 1389 if (getPamModuleConfValue(faillockConfigFile, maxFailedAttempt, valueStr) != 1390 success) 1391 { 1392 AccountPolicyIface::maxLoginAttemptBeforeLockout(0); 1393 } 1394 else 1395 { 1396 uint16_t value16 = 0; 1397 try 1398 { 1399 tmp = std::stoul(valueStr, nullptr); 1400 if (tmp > std::numeric_limits<decltype(value16)>::max()) 1401 { 1402 throw std::out_of_range("Out of range"); 1403 } 1404 value16 = static_cast<decltype(value16)>(tmp); 1405 } 1406 catch (const std::exception& e) 1407 { 1408 lg2::error("Exception for MaxLoginAttemptBeforLockout: {ERR}", 1409 "ERR", e); 1410 throw; 1411 } 1412 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16); 1413 } 1414 valueStr.clear(); 1415 if (getPamModuleConfValue(faillockConfigFile, unlockTimeout, valueStr) != 1416 success) 1417 { 1418 AccountPolicyIface::accountUnlockTimeout(0); 1419 } 1420 else 1421 { 1422 uint32_t value32 = 0; 1423 try 1424 { 1425 tmp = std::stoul(valueStr, nullptr); 1426 if (tmp > std::numeric_limits<decltype(value32)>::max()) 1427 { 1428 throw std::out_of_range("Out of range"); 1429 } 1430 value32 = static_cast<decltype(value32)>(tmp); 1431 } 1432 catch (const std::exception& e) 1433 { 1434 lg2::error("Exception for AccountUnlockTimeout: {ERR}", "ERR", e); 1435 throw; 1436 } 1437 AccountPolicyIface::accountUnlockTimeout(value32); 1438 } 1439 } 1440 1441 void UserMgr::initUserObjects(void) 1442 { 1443 // All user management lock has to be based on /etc/shadow 1444 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 1445 std::vector<std::string> userNameList; 1446 std::vector<std::string> sshGrpUsersList; 1447 UserSSHLists userSSHLists = getUserAndSshGrpList(); 1448 userNameList = std::move(userSSHLists.first); 1449 sshGrpUsersList = std::move(userSSHLists.second); 1450 1451 if (!userNameList.empty()) 1452 { 1453 std::map<std::string, std::vector<std::string>> groupLists; 1454 // We only track users that are in the |predefinedGroups| 1455 // The other groups don't contain real BMC users. 1456 for (const char* grp : predefinedGroups) 1457 { 1458 if (grp == grpSsh) 1459 { 1460 groupLists.emplace(grp, sshGrpUsersList); 1461 } 1462 else 1463 { 1464 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1465 groupLists.emplace(grp, grpUsersList); 1466 } 1467 } 1468 for (auto& grp : privMgr) 1469 { 1470 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1471 groupLists.emplace(grp, grpUsersList); 1472 } 1473 1474 for (auto& user : userNameList) 1475 { 1476 std::vector<std::string> userGroups; 1477 std::string userPriv; 1478 for (const auto& grp : groupLists) 1479 { 1480 std::vector<std::string> tempGrp = grp.second; 1481 if (std::find(tempGrp.begin(), tempGrp.end(), user) != 1482 tempGrp.end()) 1483 { 1484 if (std::find(privMgr.begin(), privMgr.end(), grp.first) != 1485 privMgr.end()) 1486 { 1487 userPriv = grp.first; 1488 } 1489 else 1490 { 1491 userGroups.emplace_back(grp.first); 1492 } 1493 } 1494 } 1495 // Add user objects to the Users path. 1496 sdbusplus::message::object_path tempObjPath(usersObjPath); 1497 tempObjPath /= user; 1498 std::string objPath(tempObjPath); 1499 std::sort(userGroups.begin(), userGroups.end()); 1500 usersList.emplace(user, std::make_unique<phosphor::user::Users>( 1501 bus, objPath.c_str(), userGroups, 1502 userPriv, isUserEnabled(user), *this)); 1503 } 1504 } 1505 } 1506 1507 void UserMgr::load() 1508 { 1509 std::optional<std::string> authTypeStr; 1510 if (std::filesystem::exists(mfaConfPath)) 1511 { 1512 serializer.load(); 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