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 <shadow.h> 18 #include <unistd.h> 19 #include <sys/types.h> 20 #include <sys/wait.h> 21 #include <fstream> 22 #include <grp.h> 23 #include <pwd.h> 24 #include <regex> 25 #include <algorithm> 26 #include <numeric> 27 #include <boost/process/child.hpp> 28 #include <boost/process/io.hpp> 29 #include <boost/algorithm/string/split.hpp> 30 #include <xyz/openbmc_project/Common/error.hpp> 31 #include <xyz/openbmc_project/User/Common/error.hpp> 32 #include <phosphor-logging/log.hpp> 33 #include <phosphor-logging/elog.hpp> 34 #include <phosphor-logging/elog-errors.hpp> 35 #include "shadowlock.hpp" 36 #include "file.hpp" 37 #include "user_mgr.hpp" 38 #include "users.hpp" 39 #include "config.h" 40 41 namespace phosphor 42 { 43 namespace user 44 { 45 46 static constexpr const char *passwdFileName = "/etc/passwd"; 47 static constexpr size_t ipmiMaxUsers = 15; 48 static constexpr size_t ipmiMaxUserNameLen = 16; 49 static constexpr size_t systemMaxUserNameLen = 30; 50 static constexpr size_t maxSystemUsers = 30; 51 static constexpr const char *grpSsh = "ssh"; 52 static constexpr uint8_t minPasswdLength = 8; 53 static constexpr int success = 0; 54 static constexpr int failure = -1; 55 56 // pam modules related 57 static constexpr const char *pamTally2 = "pam_tally2.so"; 58 static constexpr const char *pamCrackLib = "pam_cracklib.so"; 59 static constexpr const char *pamPWHistory = "pam_pwhistory.so"; 60 static constexpr const char *minPasswdLenProp = "minlen"; 61 static constexpr const char *remOldPasswdCount = "remember"; 62 static constexpr const char *maxFailedAttempt = "deny"; 63 static constexpr const char *unlockTimeout = "unlock_time"; 64 static constexpr const char *pamPasswdConfigFile = "/etc/pam.d/common-password"; 65 static constexpr const char *pamAuthConfigFile = "/etc/pam.d/common-auth"; 66 67 using namespace phosphor::logging; 68 using InsufficientPermission = 69 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission; 70 using InternalFailure = 71 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 72 using InvalidArgument = 73 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; 74 using UserNameExists = 75 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists; 76 using UserNameDoesNotExist = 77 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist; 78 using UserNameGroupFail = 79 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail; 80 81 using NoResource = 82 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource; 83 84 using Argument = xyz::openbmc_project::Common::InvalidArgument; 85 86 template <typename... ArgTypes> 87 static std::vector<std::string> executeCmd(const char *path, 88 ArgTypes &&... tArgs) 89 { 90 std::vector<std::string> stdOutput; 91 boost::process::ipstream stdOutStream; 92 boost::process::child execProg(path, const_cast<char *>(tArgs)..., 93 boost::process::std_out > stdOutStream); 94 std::string stdOutLine; 95 96 while (stdOutStream && std::getline(stdOutStream, stdOutLine) && 97 !stdOutLine.empty()) 98 { 99 stdOutput.emplace_back(stdOutLine); 100 } 101 102 execProg.wait(); 103 104 int retCode = execProg.exit_code(); 105 if (retCode) 106 { 107 log<level::ERR>("Command execution failed", entry("PATH=%d", path), 108 entry("RETURN_CODE:%d", retCode)); 109 elog<InternalFailure>(); 110 } 111 112 return stdOutput; 113 } 114 115 static std::string getCSVFromVector(std::vector<std::string> vec) 116 { 117 switch (vec.size()) 118 { 119 case 0: 120 { 121 return ""; 122 } 123 break; 124 125 case 1: 126 { 127 return std::string{vec[0]}; 128 } 129 break; 130 131 default: 132 { 133 return std::accumulate( 134 std::next(vec.begin()), vec.end(), vec[0], 135 [](std::string a, std::string b) { return a + ',' + b; }); 136 } 137 } 138 } 139 140 static bool removeStringFromCSV(std::string &csvStr, const std::string &delStr) 141 { 142 std::string::size_type delStrPos = csvStr.find(delStr); 143 if (delStrPos != std::string::npos) 144 { 145 // need to also delete the comma char 146 if (delStrPos == 0) 147 { 148 csvStr.erase(delStrPos, delStr.size() + 1); 149 } 150 else 151 { 152 csvStr.erase(delStrPos - 1, delStr.size() + 1); 153 } 154 return true; 155 } 156 return false; 157 } 158 159 bool UserMgr::isUserExist(const std::string &userName) 160 { 161 if (userName.empty()) 162 { 163 log<level::ERR>("User name is empty"); 164 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 165 Argument::ARGUMENT_VALUE("Null")); 166 } 167 if (usersList.find(userName) == usersList.end()) 168 { 169 return false; 170 } 171 return true; 172 } 173 174 void UserMgr::throwForUserDoesNotExist(const std::string &userName) 175 { 176 if (isUserExist(userName) == false) 177 { 178 log<level::ERR>("User does not exist", 179 entry("USER_NAME=%s", userName.c_str())); 180 elog<UserNameDoesNotExist>(); 181 } 182 } 183 184 void UserMgr::throwForUserExists(const std::string &userName) 185 { 186 if (isUserExist(userName) == true) 187 { 188 log<level::ERR>("User already exists", 189 entry("USER_NAME=%s", userName.c_str())); 190 elog<UserNameExists>(); 191 } 192 } 193 194 void UserMgr::throwForUserNameConstraints( 195 const std::string &userName, const std::vector<std::string> &groupNames) 196 { 197 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != 198 groupNames.end()) 199 { 200 if (userName.length() > ipmiMaxUserNameLen) 201 { 202 log<level::ERR>("IPMI user name length limitation", 203 entry("SIZE=%d", userName.length())); 204 elog<UserNameGroupFail>( 205 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON( 206 "IPMI length")); 207 } 208 } 209 if (userName.length() > systemMaxUserNameLen) 210 { 211 log<level::ERR>("User name length limitation", 212 entry("SIZE=%d", userName.length())); 213 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 214 Argument::ARGUMENT_VALUE("Invalid length")); 215 } 216 if (!std::regex_match(userName.c_str(), 217 std::regex("[a-zA-z_][a-zA-Z_0-9]*"))) 218 { 219 log<level::ERR>("Invalid user name", 220 entry("USER_NAME=%s", userName.c_str())); 221 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 222 Argument::ARGUMENT_VALUE("Invalid data")); 223 } 224 } 225 226 void UserMgr::throwForMaxGrpUserCount( 227 const std::vector<std::string> &groupNames) 228 { 229 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != 230 groupNames.end()) 231 { 232 if (getIpmiUsersCount() >= ipmiMaxUsers) 233 { 234 log<level::ERR>("IPMI user limit reached"); 235 elog<NoResource>( 236 xyz::openbmc_project::User::Common::NoResource::REASON( 237 "ipmi user count reached")); 238 } 239 } 240 else 241 { 242 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >= 243 (maxSystemUsers - ipmiMaxUsers)) 244 { 245 log<level::ERR>("Non-ipmi User limit reached"); 246 elog<NoResource>( 247 xyz::openbmc_project::User::Common::NoResource::REASON( 248 "Non-ipmi user count reached")); 249 } 250 } 251 return; 252 } 253 254 void UserMgr::throwForInvalidPrivilege(const std::string &priv) 255 { 256 if (!priv.empty() && 257 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end())) 258 { 259 log<level::ERR>("Invalid privilege"); 260 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"), 261 Argument::ARGUMENT_VALUE(priv.c_str())); 262 } 263 } 264 265 void UserMgr::throwForInvalidGroups(const std::vector<std::string> &groupNames) 266 { 267 for (auto &group : groupNames) 268 { 269 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) == 270 groupsMgr.end()) 271 { 272 log<level::ERR>("Invalid Group Name listed"); 273 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"), 274 Argument::ARGUMENT_VALUE(group.c_str())); 275 } 276 } 277 } 278 279 void UserMgr::createUser(std::string userName, 280 std::vector<std::string> groupNames, std::string priv, 281 bool enabled) 282 { 283 throwForInvalidPrivilege(priv); 284 throwForInvalidGroups(groupNames); 285 // All user management lock has to be based on /etc/shadow 286 phosphor::user::shadow::Lock lock(); 287 throwForUserExists(userName); 288 throwForUserNameConstraints(userName, groupNames); 289 throwForMaxGrpUserCount(groupNames); 290 291 std::string groups = getCSVFromVector(groupNames); 292 bool sshRequested = removeStringFromCSV(groups, grpSsh); 293 294 // treat privilege as a group - This is to avoid using different file to 295 // store the same. 296 if (!priv.empty()) 297 { 298 if (groups.size() != 0) 299 { 300 groups += ","; 301 } 302 groups += priv; 303 } 304 try 305 { 306 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(), 307 "-m", "-N", "-s", 308 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e", 309 (enabled ? "" : "1970-01-02")); 310 } 311 catch (const InternalFailure &e) 312 { 313 log<level::ERR>("Unable to create new user"); 314 elog<InternalFailure>(); 315 } 316 317 // Add the users object before sending out the signal 318 std::string userObj = std::string(usersObjPath) + "/" + userName; 319 std::sort(groupNames.begin(), groupNames.end()); 320 usersList.emplace( 321 userName, std::move(std::make_unique<phosphor::user::Users>( 322 bus, userObj.c_str(), groupNames, priv, enabled, *this))); 323 324 log<level::INFO>("User created successfully", 325 entry("USER_NAME=%s", userName.c_str())); 326 return; 327 } 328 329 void UserMgr::deleteUser(std::string userName) 330 { 331 // All user management lock has to be based on /etc/shadow 332 phosphor::user::shadow::Lock lock(); 333 throwForUserDoesNotExist(userName); 334 try 335 { 336 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r"); 337 } 338 catch (const InternalFailure &e) 339 { 340 log<level::ERR>("User delete failed", 341 entry("USER_NAME=%s", userName.c_str())); 342 elog<InternalFailure>(); 343 } 344 345 usersList.erase(userName); 346 347 log<level::INFO>("User deleted successfully", 348 entry("USER_NAME=%s", userName.c_str())); 349 return; 350 } 351 352 void UserMgr::renameUser(std::string userName, std::string newUserName) 353 { 354 // All user management lock has to be based on /etc/shadow 355 phosphor::user::shadow::Lock lock(); 356 throwForUserDoesNotExist(userName); 357 throwForUserExists(newUserName); 358 throwForUserNameConstraints(newUserName, 359 usersList[userName].get()->userGroups()); 360 try 361 { 362 std::string newHomeDir = "/home/" + newUserName; 363 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(), 364 userName.c_str(), "-d", newHomeDir.c_str(), "-m"); 365 } 366 catch (const InternalFailure &e) 367 { 368 log<level::ERR>("User rename failed", 369 entry("USER_NAME=%s", userName.c_str())); 370 elog<InternalFailure>(); 371 } 372 const auto &user = usersList[userName]; 373 std::string priv = user.get()->userPrivilege(); 374 std::vector<std::string> groupNames = user.get()->userGroups(); 375 bool enabled = user.get()->userEnabled(); 376 std::string newUserObj = std::string(usersObjPath) + "/" + newUserName; 377 // Special group 'ipmi' needs a way to identify user renamed, in order to 378 // update encrypted password. It can't rely only on InterfacesRemoved & 379 // InterfacesAdded. So first send out userRenamed signal. 380 this->userRenamed(userName, newUserName); 381 usersList.erase(userName); 382 usersList.emplace( 383 newUserName, 384 std::move(std::make_unique<phosphor::user::Users>( 385 bus, newUserObj.c_str(), groupNames, priv, enabled, *this))); 386 return; 387 } 388 389 void UserMgr::updateGroupsAndPriv(const std::string &userName, 390 const std::vector<std::string> &groupNames, 391 const std::string &priv) 392 { 393 throwForInvalidPrivilege(priv); 394 throwForInvalidGroups(groupNames); 395 // All user management lock has to be based on /etc/shadow 396 phosphor::user::shadow::Lock lock(); 397 throwForUserDoesNotExist(userName); 398 const std::vector<std::string> &oldGroupNames = 399 usersList[userName].get()->userGroups(); 400 std::vector<std::string> groupDiff; 401 // Note: already dealing with sorted group lists. 402 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(), 403 groupNames.begin(), groupNames.end(), 404 std::back_inserter(groupDiff)); 405 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") != 406 groupDiff.end()) 407 { 408 throwForUserNameConstraints(userName, groupNames); 409 throwForMaxGrpUserCount(groupNames); 410 } 411 412 std::string groups = getCSVFromVector(groupNames); 413 bool sshRequested = removeStringFromCSV(groups, grpSsh); 414 415 // treat privilege as a group - This is to avoid using different file to 416 // store the same. 417 if (!priv.empty()) 418 { 419 if (groups.size() != 0) 420 { 421 groups += ","; 422 } 423 groups += priv; 424 } 425 try 426 { 427 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(), 428 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin")); 429 } 430 catch (const InternalFailure &e) 431 { 432 log<level::ERR>("Unable to modify user privilege / groups"); 433 elog<InternalFailure>(); 434 } 435 436 log<level::INFO>("User groups / privilege updated successfully", 437 entry("USER_NAME=%s", userName.c_str())); 438 return; 439 } 440 441 uint8_t UserMgr::minPasswordLength(uint8_t value) 442 { 443 if (value == AccountPolicyIface::minPasswordLength()) 444 { 445 return value; 446 } 447 if (value < minPasswdLength) 448 { 449 return value; 450 } 451 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp, 452 std::to_string(value)) != success) 453 { 454 log<level::ERR>("Unable to set minPasswordLength"); 455 elog<InternalFailure>(); 456 } 457 return AccountPolicyIface::minPasswordLength(value); 458 } 459 460 uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value) 461 { 462 if (value == AccountPolicyIface::rememberOldPasswordTimes()) 463 { 464 return value; 465 } 466 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount, 467 std::to_string(value)) != success) 468 { 469 log<level::ERR>("Unable to set rememberOldPasswordTimes"); 470 elog<InternalFailure>(); 471 } 472 return AccountPolicyIface::rememberOldPasswordTimes(value); 473 } 474 475 uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value) 476 { 477 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout()) 478 { 479 return value; 480 } 481 if (setPamModuleArgValue(pamTally2, maxFailedAttempt, 482 std::to_string(value)) != success) 483 { 484 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout"); 485 elog<InternalFailure>(); 486 } 487 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value); 488 } 489 490 uint32_t UserMgr::accountUnlockTimeout(uint32_t value) 491 { 492 if (value == AccountPolicyIface::accountUnlockTimeout()) 493 { 494 return value; 495 } 496 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) != 497 success) 498 { 499 log<level::ERR>("Unable to set accountUnlockTimeout"); 500 elog<InternalFailure>(); 501 } 502 return AccountPolicyIface::accountUnlockTimeout(value); 503 } 504 505 int UserMgr::getPamModuleArgValue(const std::string &moduleName, 506 const std::string &argName, 507 std::string &argValue) 508 { 509 std::string fileName; 510 if (moduleName == pamTally2) 511 { 512 fileName = pamAuthConfigFile; 513 } 514 else 515 { 516 fileName = pamPasswdConfigFile; 517 } 518 std::ifstream fileToRead(fileName, std::ios::in); 519 if (!fileToRead.is_open()) 520 { 521 log<level::ERR>("Failed to open pam configuration file", 522 entry("FILE_NAME=%s", fileName.c_str())); 523 return failure; 524 } 525 std::string line; 526 auto argSearch = argName + "="; 527 size_t startPos = 0; 528 size_t endPos = 0; 529 while (getline(fileToRead, line)) 530 { 531 // skip comments section starting with # 532 if ((startPos = line.find('#')) != std::string::npos) 533 { 534 if (startPos == 0) 535 { 536 continue; 537 } 538 // skip comments after meaningful section and process those 539 line = line.substr(0, startPos); 540 } 541 if (line.find(moduleName) != std::string::npos) 542 { 543 if ((startPos = line.find(argSearch)) != std::string::npos) 544 { 545 if ((endPos = line.find(' ', startPos)) == std::string::npos) 546 { 547 endPos = line.size(); 548 } 549 startPos += argSearch.size(); 550 argValue = line.substr(startPos, endPos - startPos); 551 return success; 552 } 553 } 554 } 555 return failure; 556 } 557 558 int UserMgr::setPamModuleArgValue(const std::string &moduleName, 559 const std::string &argName, 560 const std::string &argValue) 561 { 562 std::string fileName; 563 if (moduleName == pamTally2) 564 { 565 fileName = pamAuthConfigFile; 566 } 567 else 568 { 569 fileName = pamPasswdConfigFile; 570 } 571 std::string tmpFileName = fileName + "_tmp"; 572 std::ifstream fileToRead(fileName, std::ios::in); 573 std::ofstream fileToWrite(tmpFileName, std::ios::out); 574 if (!fileToRead.is_open() || !fileToWrite.is_open()) 575 { 576 log<level::ERR>("Failed to open pam configuration /tmp file", 577 entry("FILE_NAME=%s", fileName.c_str())); 578 return failure; 579 } 580 std::string line; 581 auto argSearch = argName + "="; 582 size_t startPos = 0; 583 size_t endPos = 0; 584 bool found = false; 585 while (getline(fileToRead, line)) 586 { 587 // skip comments section starting with # 588 if ((startPos = line.find('#')) != std::string::npos) 589 { 590 if (startPos == 0) 591 { 592 fileToWrite << line << std::endl; 593 continue; 594 } 595 // skip comments after meaningful section and process those 596 line = line.substr(0, startPos); 597 } 598 if (line.find(moduleName) != std::string::npos) 599 { 600 if ((startPos = line.find(argSearch)) != std::string::npos) 601 { 602 if ((endPos = line.find(' ', startPos)) == std::string::npos) 603 { 604 endPos = line.size(); 605 } 606 startPos += argSearch.size(); 607 fileToWrite << line.substr(0, startPos) << argValue 608 << line.substr(endPos, line.size() - endPos) 609 << std::endl; 610 found = true; 611 continue; 612 } 613 } 614 fileToWrite << line << std::endl; 615 } 616 fileToWrite.close(); 617 fileToRead.close(); 618 if (found) 619 { 620 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0) 621 { 622 return success; 623 } 624 } 625 return failure; 626 } 627 628 void UserMgr::userEnable(const std::string &userName, bool enabled) 629 { 630 // All user management lock has to be based on /etc/shadow 631 phosphor::user::shadow::Lock lock(); 632 throwForUserDoesNotExist(userName); 633 try 634 { 635 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e", 636 (enabled ? "" : "1970-01-02")); 637 } 638 catch (const InternalFailure &e) 639 { 640 log<level::ERR>("Unable to modify user enabled state"); 641 elog<InternalFailure>(); 642 } 643 644 log<level::INFO>("User enabled/disabled state updated successfully", 645 entry("USER_NAME=%s", userName.c_str()), 646 entry("ENABLED=%d", enabled)); 647 return; 648 } 649 650 /** 651 * pam_tally2 app will provide the user failure count and failure status 652 * in second line of output with words position [0] - user name, 653 * [1] - failure count, [2] - latest timestamp, [3] - failure timestamp 654 * [4] - failure app 655 **/ 656 657 static constexpr size_t t2UserIdx = 0; 658 static constexpr size_t t2FailCntIdx = 1; 659 static constexpr size_t t2OutputIndex = 1; 660 661 bool UserMgr::userLockedForFailedAttempt(const std::string &userName) 662 { 663 // All user management lock has to be based on /etc/shadow 664 phosphor::user::shadow::Lock lock(); 665 std::vector<std::string> output; 666 667 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str()); 668 669 std::vector<std::string> splitWords; 670 boost::algorithm::split(splitWords, output[t2OutputIndex], 671 boost::algorithm::is_any_of("\t "), 672 boost::token_compress_on); 673 674 if (splitWords[t2UserIdx] == userName) 675 { 676 try 677 { 678 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr); 679 uint16_t value16 = 0; 680 if (tmp > std::numeric_limits<decltype(value16)>::max()) 681 { 682 throw std::out_of_range("Out of range"); 683 } 684 value16 = static_cast<decltype(value16)>(tmp); 685 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 && 686 value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout()) 687 { 688 return true; // User account is locked out 689 } 690 return false; // User account is un-locked 691 } 692 catch (const std::exception &e) 693 { 694 log<level::ERR>("Exception for userLockedForFailedAttempt", 695 entry("WHAT=%s", e.what())); 696 throw; 697 } 698 } 699 log<level::ERR>("Unable to get user account failed attempt", 700 entry("USER_NAME=%s", userName.c_str())); 701 elog<InternalFailure>(); 702 return false; 703 } 704 705 bool UserMgr::userLockedForFailedAttempt(const std::string &userName, 706 const bool &value) 707 { 708 // All user management lock has to be based on /etc/shadow 709 phosphor::user::shadow::Lock lock(); 710 std::vector<std::string> output; 711 if (value == true) 712 { 713 return userLockedForFailedAttempt(userName); 714 } 715 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r"); 716 717 std::vector<std::string> splitWords; 718 boost::algorithm::split(splitWords, output[t2OutputIndex], 719 boost::algorithm::is_any_of("\t "), 720 boost::token_compress_on); 721 722 if (splitWords[t2UserIdx] == userName) 723 { 724 return userLockedForFailedAttempt(userName); 725 } 726 log<level::ERR>("Unable to clear user account failed attempt"); 727 elog<InternalFailure>(); 728 return false; 729 } 730 731 UserSSHLists UserMgr::getUserAndSshGrpList() 732 { 733 // All user management lock has to be based on /etc/shadow 734 phosphor::user::shadow::Lock lock(); 735 736 std::vector<std::string> userList; 737 std::vector<std::string> sshUsersList; 738 struct passwd pw, *pwp = nullptr; 739 std::array<char, 1024> buffer{}; 740 741 phosphor::user::File passwd(passwdFileName, "r"); 742 if ((passwd)() == NULL) 743 { 744 log<level::ERR>("Error opening the passwd file"); 745 elog<InternalFailure>(); 746 } 747 748 while (true) 749 { 750 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(), 751 &pwp); 752 if ((r != 0) || (pwp == NULL)) 753 { 754 // Any error, break the loop. 755 break; 756 } 757 // Add all users whose UID >= 1000 and < 65534 758 // and special UID 0. 759 if ((pwp->pw_uid == 0) || 760 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))) 761 { 762 std::string userName(pwp->pw_name); 763 userList.emplace_back(userName); 764 765 // ssh doesn't have separate group. Check login shell entry to 766 // get all users list which are member of ssh group. 767 std::string loginShell(pwp->pw_shell); 768 if (loginShell == "/bin/sh") 769 { 770 sshUsersList.emplace_back(userName); 771 } 772 } 773 } 774 endpwent(); 775 return std::make_pair(std::move(userList), std::move(sshUsersList)); 776 } 777 778 size_t UserMgr::getIpmiUsersCount() 779 { 780 std::vector<std::string> userList = getUsersInGroup("ipmi"); 781 return userList.size(); 782 } 783 784 bool UserMgr::isUserEnabled(const std::string &userName) 785 { 786 // All user management lock has to be based on /etc/shadow 787 phosphor::user::shadow::Lock lock(); 788 std::array<char, 4096> buffer{}; 789 struct spwd spwd; 790 struct spwd *resultPtr = nullptr; 791 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(), 792 buffer.max_size(), &resultPtr); 793 if (!status && (&spwd == resultPtr)) 794 { 795 if (resultPtr->sp_expire >= 0) 796 { 797 return false; // user locked out 798 } 799 return true; 800 } 801 return false; // assume user is disabled for any error. 802 } 803 804 std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName) 805 { 806 std::vector<std::string> usersInGroup; 807 // Should be more than enough to get the pwd structure. 808 std::array<char, 4096> buffer{}; 809 struct group grp; 810 struct group *resultPtr = nullptr; 811 812 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(), 813 buffer.max_size(), &resultPtr); 814 815 if (!status && (&grp == resultPtr)) 816 { 817 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem)) 818 { 819 usersInGroup.emplace_back(*(grp.gr_mem)); 820 } 821 } 822 else 823 { 824 log<level::ERR>("Group not found", 825 entry("GROUP=%s", groupName.c_str())); 826 // Don't throw error, just return empty userList - fallback 827 } 828 return usersInGroup; 829 } 830 831 void UserMgr::initUserObjects(void) 832 { 833 // All user management lock has to be based on /etc/shadow 834 phosphor::user::shadow::Lock lock(); 835 std::vector<std::string> userNameList; 836 std::vector<std::string> sshGrpUsersList; 837 UserSSHLists userSSHLists = getUserAndSshGrpList(); 838 userNameList = std::move(userSSHLists.first); 839 sshGrpUsersList = std::move(userSSHLists.second); 840 841 if (!userNameList.empty()) 842 { 843 std::map<std::string, std::vector<std::string>> groupLists; 844 for (auto &grp : groupsMgr) 845 { 846 if (grp == grpSsh) 847 { 848 groupLists.emplace(grp, sshGrpUsersList); 849 } 850 else 851 { 852 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 853 groupLists.emplace(grp, grpUsersList); 854 } 855 } 856 for (auto &grp : privMgr) 857 { 858 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 859 groupLists.emplace(grp, grpUsersList); 860 } 861 862 for (auto &user : userNameList) 863 { 864 std::vector<std::string> userGroups; 865 std::string userPriv; 866 for (const auto &grp : groupLists) 867 { 868 std::vector<std::string> tempGrp = grp.second; 869 if (std::find(tempGrp.begin(), tempGrp.end(), user) != 870 tempGrp.end()) 871 { 872 if (std::find(privMgr.begin(), privMgr.end(), grp.first) != 873 privMgr.end()) 874 { 875 userPriv = grp.first; 876 } 877 else 878 { 879 userGroups.emplace_back(grp.first); 880 } 881 } 882 } 883 // Add user objects to the Users path. 884 auto objPath = std::string(usersObjPath) + "/" + user; 885 std::sort(userGroups.begin(), userGroups.end()); 886 usersList.emplace(user, 887 std::move(std::make_unique<phosphor::user::Users>( 888 bus, objPath.c_str(), userGroups, userPriv, 889 isUserEnabled(user), *this))); 890 } 891 } 892 } 893 894 UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) : 895 UserMgrIface(bus, path), AccountPolicyIface(bus, path), bus(bus), path(path) 896 { 897 UserMgrIface::allPrivileges(privMgr); 898 std::sort(groupsMgr.begin(), groupsMgr.end()); 899 UserMgrIface::allGroups(groupsMgr); 900 std::string valueStr; 901 auto value = minPasswdLength; 902 unsigned long tmp = 0; 903 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) != 904 success) 905 { 906 AccountPolicyIface::minPasswordLength(minPasswdLength); 907 } 908 else 909 { 910 try 911 { 912 tmp = std::stoul(valueStr, nullptr); 913 if (tmp > std::numeric_limits<decltype(value)>::max()) 914 { 915 throw std::out_of_range("Out of range"); 916 } 917 value = static_cast<decltype(value)>(tmp); 918 } 919 catch (const std::exception &e) 920 { 921 log<level::ERR>("Exception for MinPasswordLength", 922 entry("WHAT=%s", e.what())); 923 throw; 924 } 925 AccountPolicyIface::minPasswordLength(value); 926 } 927 valueStr.clear(); 928 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) != 929 success) 930 { 931 AccountPolicyIface::rememberOldPasswordTimes(0); 932 } 933 else 934 { 935 value = 0; 936 try 937 { 938 tmp = std::stoul(valueStr, nullptr); 939 if (tmp > std::numeric_limits<decltype(value)>::max()) 940 { 941 throw std::out_of_range("Out of range"); 942 } 943 value = static_cast<decltype(value)>(tmp); 944 } 945 catch (const std::exception &e) 946 { 947 log<level::ERR>("Exception for RememberOldPasswordTimes", 948 entry("WHAT=%s", e.what())); 949 throw; 950 } 951 AccountPolicyIface::rememberOldPasswordTimes(value); 952 } 953 valueStr.clear(); 954 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success) 955 { 956 AccountPolicyIface::maxLoginAttemptBeforeLockout(0); 957 } 958 else 959 { 960 uint16_t value16 = 0; 961 try 962 { 963 tmp = std::stoul(valueStr, nullptr); 964 if (tmp > std::numeric_limits<decltype(value16)>::max()) 965 { 966 throw std::out_of_range("Out of range"); 967 } 968 value16 = static_cast<decltype(value16)>(tmp); 969 } 970 catch (const std::exception &e) 971 { 972 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout", 973 entry("WHAT=%s", e.what())); 974 throw; 975 } 976 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16); 977 } 978 valueStr.clear(); 979 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success) 980 { 981 AccountPolicyIface::accountUnlockTimeout(0); 982 } 983 else 984 { 985 uint32_t value32 = 0; 986 try 987 { 988 tmp = std::stoul(valueStr, nullptr); 989 if (tmp > std::numeric_limits<decltype(value32)>::max()) 990 { 991 throw std::out_of_range("Out of range"); 992 } 993 value32 = static_cast<decltype(value32)>(tmp); 994 } 995 catch (const std::exception &e) 996 { 997 log<level::ERR>("Exception for AccountUnlockTimeout", 998 entry("WHAT=%s", e.what())); 999 throw; 1000 } 1001 AccountPolicyIface::accountUnlockTimeout(value32); 1002 } 1003 initUserObjects(); 1004 } 1005 1006 } // namespace user 1007 } // namespace phosphor 1008