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 <time.h> 22 #include <fstream> 23 #include <grp.h> 24 #include <pwd.h> 25 #include <regex> 26 #include <algorithm> 27 #include <numeric> 28 #include <boost/process/child.hpp> 29 #include <boost/process/io.hpp> 30 #include <boost/algorithm/string/split.hpp> 31 #include <xyz/openbmc_project/Common/error.hpp> 32 #include <xyz/openbmc_project/User/Common/error.hpp> 33 #include <phosphor-logging/log.hpp> 34 #include <phosphor-logging/elog.hpp> 35 #include <phosphor-logging/elog-errors.hpp> 36 #include "shadowlock.hpp" 37 #include "file.hpp" 38 #include "user_mgr.hpp" 39 #include "users.hpp" 40 #include "config.h" 41 42 namespace phosphor 43 { 44 namespace user 45 { 46 47 static constexpr const char *passwdFileName = "/etc/passwd"; 48 static constexpr size_t ipmiMaxUsers = 15; 49 static constexpr size_t ipmiMaxUserNameLen = 16; 50 static constexpr size_t systemMaxUserNameLen = 30; 51 static constexpr size_t maxSystemUsers = 30; 52 static constexpr const char *grpSsh = "ssh"; 53 static constexpr uint8_t minPasswdLength = 8; 54 static constexpr int success = 0; 55 static constexpr int failure = -1; 56 57 // pam modules related 58 static constexpr const char *pamTally2 = "pam_tally2.so"; 59 static constexpr const char *pamCrackLib = "pam_cracklib.so"; 60 static constexpr const char *pamPWHistory = "pam_pwhistory.so"; 61 static constexpr const char *minPasswdLenProp = "minlen"; 62 static constexpr const char *remOldPasswdCount = "remember"; 63 static constexpr const char *maxFailedAttempt = "deny"; 64 static constexpr const char *unlockTimeout = "unlock_time"; 65 static constexpr const char *pamPasswdConfigFile = "/etc/pam.d/common-password"; 66 static constexpr const char *pamAuthConfigFile = "/etc/pam.d/common-auth"; 67 68 // Object Manager related 69 static constexpr const char *ldapMgrObjBasePath = 70 "/xyz/openbmc_project/user/ldap"; 71 72 // Object Mapper related 73 static constexpr const char *objMapperService = 74 "xyz.openbmc_project.ObjectMapper"; 75 static constexpr const char *objMapperPath = 76 "/xyz/openbmc_project/object_mapper"; 77 static constexpr const char *objMapperInterface = 78 "xyz.openbmc_project.ObjectMapper"; 79 80 using namespace phosphor::logging; 81 using InsufficientPermission = 82 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission; 83 using InternalFailure = 84 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 85 using InvalidArgument = 86 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; 87 using UserNameExists = 88 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists; 89 using UserNameDoesNotExist = 90 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist; 91 using UserNameGroupFail = 92 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail; 93 using NoResource = 94 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource; 95 96 using Argument = xyz::openbmc_project::Common::InvalidArgument; 97 98 template <typename... ArgTypes> 99 static std::vector<std::string> executeCmd(const char *path, 100 ArgTypes &&... tArgs) 101 { 102 std::vector<std::string> stdOutput; 103 boost::process::ipstream stdOutStream; 104 boost::process::child execProg(path, const_cast<char *>(tArgs)..., 105 boost::process::std_out > stdOutStream); 106 std::string stdOutLine; 107 108 while (stdOutStream && std::getline(stdOutStream, stdOutLine) && 109 !stdOutLine.empty()) 110 { 111 stdOutput.emplace_back(stdOutLine); 112 } 113 114 execProg.wait(); 115 116 int retCode = execProg.exit_code(); 117 if (retCode) 118 { 119 log<level::ERR>("Command execution failed", entry("PATH=%d", path), 120 entry("RETURN_CODE:%d", retCode)); 121 elog<InternalFailure>(); 122 } 123 124 return stdOutput; 125 } 126 127 static std::string getCSVFromVector(std::vector<std::string> vec) 128 { 129 switch (vec.size()) 130 { 131 case 0: 132 { 133 return ""; 134 } 135 break; 136 137 case 1: 138 { 139 return std::string{vec[0]}; 140 } 141 break; 142 143 default: 144 { 145 return std::accumulate( 146 std::next(vec.begin()), vec.end(), vec[0], 147 [](std::string a, std::string b) { return a + ',' + b; }); 148 } 149 } 150 } 151 152 static bool removeStringFromCSV(std::string &csvStr, const std::string &delStr) 153 { 154 std::string::size_type delStrPos = csvStr.find(delStr); 155 if (delStrPos != std::string::npos) 156 { 157 // need to also delete the comma char 158 if (delStrPos == 0) 159 { 160 csvStr.erase(delStrPos, delStr.size() + 1); 161 } 162 else 163 { 164 csvStr.erase(delStrPos - 1, delStr.size() + 1); 165 } 166 return true; 167 } 168 return false; 169 } 170 171 bool UserMgr::isUserExist(const std::string &userName) 172 { 173 if (userName.empty()) 174 { 175 log<level::ERR>("User name is empty"); 176 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 177 Argument::ARGUMENT_VALUE("Null")); 178 } 179 if (usersList.find(userName) == usersList.end()) 180 { 181 return false; 182 } 183 return true; 184 } 185 186 void UserMgr::throwForUserDoesNotExist(const std::string &userName) 187 { 188 if (isUserExist(userName) == false) 189 { 190 log<level::ERR>("User does not exist", 191 entry("USER_NAME=%s", userName.c_str())); 192 elog<UserNameDoesNotExist>(); 193 } 194 } 195 196 void UserMgr::throwForUserExists(const std::string &userName) 197 { 198 if (isUserExist(userName) == true) 199 { 200 log<level::ERR>("User already exists", 201 entry("USER_NAME=%s", userName.c_str())); 202 elog<UserNameExists>(); 203 } 204 } 205 206 void UserMgr::throwForUserNameConstraints( 207 const std::string &userName, const std::vector<std::string> &groupNames) 208 { 209 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != 210 groupNames.end()) 211 { 212 if (userName.length() > ipmiMaxUserNameLen) 213 { 214 log<level::ERR>("IPMI user name length limitation", 215 entry("SIZE=%d", userName.length())); 216 elog<UserNameGroupFail>( 217 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON( 218 "IPMI length")); 219 } 220 } 221 if (userName.length() > systemMaxUserNameLen) 222 { 223 log<level::ERR>("User name length limitation", 224 entry("SIZE=%d", userName.length())); 225 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 226 Argument::ARGUMENT_VALUE("Invalid length")); 227 } 228 if (!std::regex_match(userName.c_str(), 229 std::regex("[a-zA-z_][a-zA-Z_0-9]*"))) 230 { 231 log<level::ERR>("Invalid user name", 232 entry("USER_NAME=%s", userName.c_str())); 233 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 234 Argument::ARGUMENT_VALUE("Invalid data")); 235 } 236 } 237 238 void UserMgr::throwForMaxGrpUserCount( 239 const std::vector<std::string> &groupNames) 240 { 241 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != 242 groupNames.end()) 243 { 244 if (getIpmiUsersCount() >= ipmiMaxUsers) 245 { 246 log<level::ERR>("IPMI user limit reached"); 247 elog<NoResource>( 248 xyz::openbmc_project::User::Common::NoResource::REASON( 249 "ipmi user count reached")); 250 } 251 } 252 else 253 { 254 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >= 255 (maxSystemUsers - ipmiMaxUsers)) 256 { 257 log<level::ERR>("Non-ipmi User limit reached"); 258 elog<NoResource>( 259 xyz::openbmc_project::User::Common::NoResource::REASON( 260 "Non-ipmi user count reached")); 261 } 262 } 263 return; 264 } 265 266 void UserMgr::throwForInvalidPrivilege(const std::string &priv) 267 { 268 if (!priv.empty() && 269 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end())) 270 { 271 log<level::ERR>("Invalid privilege"); 272 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"), 273 Argument::ARGUMENT_VALUE(priv.c_str())); 274 } 275 } 276 277 void UserMgr::throwForInvalidGroups(const std::vector<std::string> &groupNames) 278 { 279 for (auto &group : groupNames) 280 { 281 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) == 282 groupsMgr.end()) 283 { 284 log<level::ERR>("Invalid Group Name listed"); 285 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"), 286 Argument::ARGUMENT_VALUE(group.c_str())); 287 } 288 } 289 } 290 291 void UserMgr::createUser(std::string userName, 292 std::vector<std::string> groupNames, std::string priv, 293 bool enabled) 294 { 295 throwForInvalidPrivilege(priv); 296 throwForInvalidGroups(groupNames); 297 // All user management lock has to be based on /etc/shadow 298 phosphor::user::shadow::Lock lock(); 299 throwForUserExists(userName); 300 throwForUserNameConstraints(userName, groupNames); 301 throwForMaxGrpUserCount(groupNames); 302 303 std::string groups = getCSVFromVector(groupNames); 304 bool sshRequested = removeStringFromCSV(groups, grpSsh); 305 306 // treat privilege as a group - This is to avoid using different file to 307 // store the same. 308 if (!priv.empty()) 309 { 310 if (groups.size() != 0) 311 { 312 groups += ","; 313 } 314 groups += priv; 315 } 316 try 317 { 318 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(), 319 "-m", "-N", "-s", 320 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e", 321 (enabled ? "" : "1970-01-02")); 322 } 323 catch (const InternalFailure &e) 324 { 325 log<level::ERR>("Unable to create new user"); 326 elog<InternalFailure>(); 327 } 328 329 // Add the users object before sending out the signal 330 std::string userObj = std::string(usersObjPath) + "/" + userName; 331 std::sort(groupNames.begin(), groupNames.end()); 332 usersList.emplace( 333 userName, std::move(std::make_unique<phosphor::user::Users>( 334 bus, userObj.c_str(), groupNames, priv, enabled, *this))); 335 336 log<level::INFO>("User created successfully", 337 entry("USER_NAME=%s", userName.c_str())); 338 return; 339 } 340 341 void UserMgr::deleteUser(std::string userName) 342 { 343 // All user management lock has to be based on /etc/shadow 344 phosphor::user::shadow::Lock lock(); 345 throwForUserDoesNotExist(userName); 346 try 347 { 348 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r"); 349 } 350 catch (const InternalFailure &e) 351 { 352 log<level::ERR>("User delete failed", 353 entry("USER_NAME=%s", userName.c_str())); 354 elog<InternalFailure>(); 355 } 356 357 usersList.erase(userName); 358 359 log<level::INFO>("User deleted successfully", 360 entry("USER_NAME=%s", userName.c_str())); 361 return; 362 } 363 364 void UserMgr::renameUser(std::string userName, std::string newUserName) 365 { 366 // All user management lock has to be based on /etc/shadow 367 phosphor::user::shadow::Lock lock(); 368 throwForUserDoesNotExist(userName); 369 throwForUserExists(newUserName); 370 throwForUserNameConstraints(newUserName, 371 usersList[userName].get()->userGroups()); 372 try 373 { 374 std::string newHomeDir = "/home/" + newUserName; 375 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(), 376 userName.c_str(), "-d", newHomeDir.c_str(), "-m"); 377 } 378 catch (const InternalFailure &e) 379 { 380 log<level::ERR>("User rename failed", 381 entry("USER_NAME=%s", userName.c_str())); 382 elog<InternalFailure>(); 383 } 384 const auto &user = usersList[userName]; 385 std::string priv = user.get()->userPrivilege(); 386 std::vector<std::string> groupNames = user.get()->userGroups(); 387 bool enabled = user.get()->userEnabled(); 388 std::string newUserObj = std::string(usersObjPath) + "/" + newUserName; 389 // Special group 'ipmi' needs a way to identify user renamed, in order to 390 // update encrypted password. It can't rely only on InterfacesRemoved & 391 // InterfacesAdded. So first send out userRenamed signal. 392 this->userRenamed(userName, newUserName); 393 usersList.erase(userName); 394 usersList.emplace( 395 newUserName, 396 std::move(std::make_unique<phosphor::user::Users>( 397 bus, newUserObj.c_str(), groupNames, priv, enabled, *this))); 398 return; 399 } 400 401 void UserMgr::updateGroupsAndPriv(const std::string &userName, 402 const std::vector<std::string> &groupNames, 403 const std::string &priv) 404 { 405 throwForInvalidPrivilege(priv); 406 throwForInvalidGroups(groupNames); 407 // All user management lock has to be based on /etc/shadow 408 phosphor::user::shadow::Lock lock(); 409 throwForUserDoesNotExist(userName); 410 const std::vector<std::string> &oldGroupNames = 411 usersList[userName].get()->userGroups(); 412 std::vector<std::string> groupDiff; 413 // Note: already dealing with sorted group lists. 414 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(), 415 groupNames.begin(), groupNames.end(), 416 std::back_inserter(groupDiff)); 417 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") != 418 groupDiff.end()) 419 { 420 throwForUserNameConstraints(userName, groupNames); 421 throwForMaxGrpUserCount(groupNames); 422 } 423 424 std::string groups = getCSVFromVector(groupNames); 425 bool sshRequested = removeStringFromCSV(groups, grpSsh); 426 427 // treat privilege as a group - This is to avoid using different file to 428 // store the same. 429 if (!priv.empty()) 430 { 431 if (groups.size() != 0) 432 { 433 groups += ","; 434 } 435 groups += priv; 436 } 437 try 438 { 439 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(), 440 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin")); 441 } 442 catch (const InternalFailure &e) 443 { 444 log<level::ERR>("Unable to modify user privilege / groups"); 445 elog<InternalFailure>(); 446 } 447 448 log<level::INFO>("User groups / privilege updated successfully", 449 entry("USER_NAME=%s", userName.c_str())); 450 return; 451 } 452 453 uint8_t UserMgr::minPasswordLength(uint8_t value) 454 { 455 if (value == AccountPolicyIface::minPasswordLength()) 456 { 457 return value; 458 } 459 if (value < minPasswdLength) 460 { 461 return value; 462 } 463 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp, 464 std::to_string(value)) != success) 465 { 466 log<level::ERR>("Unable to set minPasswordLength"); 467 elog<InternalFailure>(); 468 } 469 return AccountPolicyIface::minPasswordLength(value); 470 } 471 472 uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value) 473 { 474 if (value == AccountPolicyIface::rememberOldPasswordTimes()) 475 { 476 return value; 477 } 478 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount, 479 std::to_string(value)) != success) 480 { 481 log<level::ERR>("Unable to set rememberOldPasswordTimes"); 482 elog<InternalFailure>(); 483 } 484 return AccountPolicyIface::rememberOldPasswordTimes(value); 485 } 486 487 uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value) 488 { 489 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout()) 490 { 491 return value; 492 } 493 if (setPamModuleArgValue(pamTally2, maxFailedAttempt, 494 std::to_string(value)) != success) 495 { 496 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout"); 497 elog<InternalFailure>(); 498 } 499 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value); 500 } 501 502 uint32_t UserMgr::accountUnlockTimeout(uint32_t value) 503 { 504 if (value == AccountPolicyIface::accountUnlockTimeout()) 505 { 506 return value; 507 } 508 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) != 509 success) 510 { 511 log<level::ERR>("Unable to set accountUnlockTimeout"); 512 elog<InternalFailure>(); 513 } 514 return AccountPolicyIface::accountUnlockTimeout(value); 515 } 516 517 int UserMgr::getPamModuleArgValue(const std::string &moduleName, 518 const std::string &argName, 519 std::string &argValue) 520 { 521 std::string fileName; 522 if (moduleName == pamTally2) 523 { 524 fileName = pamAuthConfigFile; 525 } 526 else 527 { 528 fileName = pamPasswdConfigFile; 529 } 530 std::ifstream fileToRead(fileName, std::ios::in); 531 if (!fileToRead.is_open()) 532 { 533 log<level::ERR>("Failed to open pam configuration file", 534 entry("FILE_NAME=%s", fileName.c_str())); 535 return failure; 536 } 537 std::string line; 538 auto argSearch = argName + "="; 539 size_t startPos = 0; 540 size_t endPos = 0; 541 while (getline(fileToRead, line)) 542 { 543 // skip comments section starting with # 544 if ((startPos = line.find('#')) != std::string::npos) 545 { 546 if (startPos == 0) 547 { 548 continue; 549 } 550 // skip comments after meaningful section and process those 551 line = line.substr(0, startPos); 552 } 553 if (line.find(moduleName) != std::string::npos) 554 { 555 if ((startPos = line.find(argSearch)) != std::string::npos) 556 { 557 if ((endPos = line.find(' ', startPos)) == std::string::npos) 558 { 559 endPos = line.size(); 560 } 561 startPos += argSearch.size(); 562 argValue = line.substr(startPos, endPos - startPos); 563 return success; 564 } 565 } 566 } 567 return failure; 568 } 569 570 int UserMgr::setPamModuleArgValue(const std::string &moduleName, 571 const std::string &argName, 572 const std::string &argValue) 573 { 574 std::string fileName; 575 if (moduleName == pamTally2) 576 { 577 fileName = pamAuthConfigFile; 578 } 579 else 580 { 581 fileName = pamPasswdConfigFile; 582 } 583 std::string tmpFileName = fileName + "_tmp"; 584 std::ifstream fileToRead(fileName, std::ios::in); 585 std::ofstream fileToWrite(tmpFileName, std::ios::out); 586 if (!fileToRead.is_open() || !fileToWrite.is_open()) 587 { 588 log<level::ERR>("Failed to open pam configuration /tmp file", 589 entry("FILE_NAME=%s", fileName.c_str())); 590 return failure; 591 } 592 std::string line; 593 auto argSearch = argName + "="; 594 size_t startPos = 0; 595 size_t endPos = 0; 596 bool found = false; 597 while (getline(fileToRead, line)) 598 { 599 // skip comments section starting with # 600 if ((startPos = line.find('#')) != std::string::npos) 601 { 602 if (startPos == 0) 603 { 604 fileToWrite << line << std::endl; 605 continue; 606 } 607 // skip comments after meaningful section and process those 608 line = line.substr(0, startPos); 609 } 610 if (line.find(moduleName) != std::string::npos) 611 { 612 if ((startPos = line.find(argSearch)) != std::string::npos) 613 { 614 if ((endPos = line.find(' ', startPos)) == std::string::npos) 615 { 616 endPos = line.size(); 617 } 618 startPos += argSearch.size(); 619 fileToWrite << line.substr(0, startPos) << argValue 620 << line.substr(endPos, line.size() - endPos) 621 << std::endl; 622 found = true; 623 continue; 624 } 625 } 626 fileToWrite << line << std::endl; 627 } 628 fileToWrite.close(); 629 fileToRead.close(); 630 if (found) 631 { 632 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0) 633 { 634 return success; 635 } 636 } 637 return failure; 638 } 639 640 void UserMgr::userEnable(const std::string &userName, bool enabled) 641 { 642 // All user management lock has to be based on /etc/shadow 643 phosphor::user::shadow::Lock lock(); 644 throwForUserDoesNotExist(userName); 645 try 646 { 647 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e", 648 (enabled ? "" : "1970-01-02")); 649 } 650 catch (const InternalFailure &e) 651 { 652 log<level::ERR>("Unable to modify user enabled state"); 653 elog<InternalFailure>(); 654 } 655 656 log<level::INFO>("User enabled/disabled state updated successfully", 657 entry("USER_NAME=%s", userName.c_str()), 658 entry("ENABLED=%d", enabled)); 659 return; 660 } 661 662 /** 663 * pam_tally2 app will provide the user failure count and failure status 664 * in second line of output with words position [0] - user name, 665 * [1] - failure count, [2] - latest timestamp, [3] - failure timestamp 666 * [4] - failure app 667 **/ 668 669 static constexpr size_t t2UserIdx = 0; 670 static constexpr size_t t2FailCntIdx = 1; 671 static constexpr size_t t2OutputIndex = 1; 672 673 bool UserMgr::userLockedForFailedAttempt(const std::string &userName) 674 { 675 // All user management lock has to be based on /etc/shadow 676 phosphor::user::shadow::Lock lock(); 677 std::vector<std::string> output; 678 679 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str()); 680 681 std::vector<std::string> splitWords; 682 boost::algorithm::split(splitWords, output[t2OutputIndex], 683 boost::algorithm::is_any_of("\t "), 684 boost::token_compress_on); 685 686 try 687 { 688 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr); 689 uint16_t value16 = 0; 690 if (tmp > std::numeric_limits<decltype(value16)>::max()) 691 { 692 throw std::out_of_range("Out of range"); 693 } 694 value16 = static_cast<decltype(value16)>(tmp); 695 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 && 696 value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout()) 697 { 698 return true; // User account is locked out 699 } 700 return false; // User account is un-locked 701 } 702 catch (const std::exception &e) 703 { 704 log<level::ERR>("Exception for userLockedForFailedAttempt", 705 entry("WHAT=%s", e.what())); 706 throw; 707 } 708 } 709 710 bool UserMgr::userLockedForFailedAttempt(const std::string &userName, 711 const bool &value) 712 { 713 // All user management lock has to be based on /etc/shadow 714 phosphor::user::shadow::Lock lock(); 715 std::vector<std::string> output; 716 if (value == true) 717 { 718 return userLockedForFailedAttempt(userName); 719 } 720 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r"); 721 722 std::vector<std::string> splitWords; 723 boost::algorithm::split(splitWords, output[t2OutputIndex], 724 boost::algorithm::is_any_of("\t "), 725 boost::token_compress_on); 726 727 return userLockedForFailedAttempt(userName); 728 } 729 730 bool UserMgr::userPasswordExpired(const std::string &userName) 731 { 732 // All user management lock has to be based on /etc/shadow 733 phosphor::user::shadow::Lock lock(); 734 735 struct spwd spwd 736 { 737 }; 738 struct spwd *spwdPtr = nullptr; 739 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); 740 if (buflen < -1) 741 { 742 // Use a default size if there is no hard limit suggested by sysconf() 743 buflen = 1024; 744 } 745 std::vector<char> buffer(buflen); 746 auto status = 747 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr); 748 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd. 749 // If no matching password record was found, these functions return 0 750 // and store NULL in *spwdPtr 751 if ((status == 0) && (&spwd == spwdPtr)) 752 { 753 // Determine password validity per "chage" docs, where: 754 // spwd.sp_lstchg == 0 means password is expired, and 755 // spwd.sp_max == -1 means the password does not expire. 756 constexpr long seconds_per_day = 60 * 60 * 24; 757 long today = static_cast<long>(time(NULL)) / seconds_per_day; 758 if ((spwd.sp_lstchg == 0) || 759 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today))) 760 { 761 return true; 762 } 763 } 764 else 765 { 766 log<level::ERR>("User does not exist", 767 entry("USER_NAME=%s", userName.c_str())); 768 elog<UserNameDoesNotExist>(); 769 } 770 771 return false; 772 } 773 774 UserSSHLists UserMgr::getUserAndSshGrpList() 775 { 776 // All user management lock has to be based on /etc/shadow 777 phosphor::user::shadow::Lock lock(); 778 779 std::vector<std::string> userList; 780 std::vector<std::string> sshUsersList; 781 struct passwd pw, *pwp = nullptr; 782 std::array<char, 1024> buffer{}; 783 784 phosphor::user::File passwd(passwdFileName, "r"); 785 if ((passwd)() == NULL) 786 { 787 log<level::ERR>("Error opening the passwd file"); 788 elog<InternalFailure>(); 789 } 790 791 while (true) 792 { 793 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(), 794 &pwp); 795 if ((r != 0) || (pwp == NULL)) 796 { 797 // Any error, break the loop. 798 break; 799 } 800 #ifdef ENABLE_ROOT_USER_MGMT 801 // Add all users whose UID >= 1000 and < 65534 802 // and special UID 0. 803 if ((pwp->pw_uid == 0) || 804 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))) 805 #else 806 // Add all users whose UID >=1000 and < 65534 807 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)) 808 #endif 809 { 810 std::string userName(pwp->pw_name); 811 userList.emplace_back(userName); 812 813 // ssh doesn't have separate group. Check login shell entry to 814 // get all users list which are member of ssh group. 815 std::string loginShell(pwp->pw_shell); 816 if (loginShell == "/bin/sh") 817 { 818 sshUsersList.emplace_back(userName); 819 } 820 } 821 } 822 endpwent(); 823 return std::make_pair(std::move(userList), std::move(sshUsersList)); 824 } 825 826 size_t UserMgr::getIpmiUsersCount() 827 { 828 std::vector<std::string> userList = getUsersInGroup("ipmi"); 829 return userList.size(); 830 } 831 832 bool UserMgr::isUserEnabled(const std::string &userName) 833 { 834 // All user management lock has to be based on /etc/shadow 835 phosphor::user::shadow::Lock lock(); 836 std::array<char, 4096> buffer{}; 837 struct spwd spwd; 838 struct spwd *resultPtr = nullptr; 839 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(), 840 buffer.max_size(), &resultPtr); 841 if (!status && (&spwd == resultPtr)) 842 { 843 if (resultPtr->sp_expire >= 0) 844 { 845 return false; // user locked out 846 } 847 return true; 848 } 849 return false; // assume user is disabled for any error. 850 } 851 852 std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName) 853 { 854 std::vector<std::string> usersInGroup; 855 // Should be more than enough to get the pwd structure. 856 std::array<char, 4096> buffer{}; 857 struct group grp; 858 struct group *resultPtr = nullptr; 859 860 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(), 861 buffer.max_size(), &resultPtr); 862 863 if (!status && (&grp == resultPtr)) 864 { 865 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem)) 866 { 867 usersInGroup.emplace_back(*(grp.gr_mem)); 868 } 869 } 870 else 871 { 872 log<level::ERR>("Group not found", 873 entry("GROUP=%s", groupName.c_str())); 874 // Don't throw error, just return empty userList - fallback 875 } 876 return usersInGroup; 877 } 878 879 DbusUserObj UserMgr::getPrivilegeMapperObject(void) 880 { 881 DbusUserObj objects; 882 try 883 { 884 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap"; 885 std::string interface = "xyz.openbmc_project.User.Ldap.Config"; 886 887 auto ldapMgmtService = 888 getServiceName(std::move(basePath), std::move(interface)); 889 auto method = bus.new_method_call( 890 ldapMgmtService.c_str(), ldapMgrObjBasePath, 891 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 892 893 auto reply = bus.call(method); 894 reply.read(objects); 895 } 896 catch (const InternalFailure &e) 897 { 898 log<level::ERR>("Unable to get the User Service", 899 entry("WHAT=%s", e.what())); 900 throw; 901 } 902 catch (const sdbusplus::exception::SdBusError &e) 903 { 904 log<level::ERR>( 905 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"), 906 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what())); 907 throw; 908 } 909 return objects; 910 } 911 912 std::string UserMgr::getLdapGroupName(const std::string &userName) 913 { 914 struct passwd pwd 915 { 916 }; 917 struct passwd *pwdPtr = nullptr; 918 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); 919 if (buflen < -1) 920 { 921 // Use a default size if there is no hard limit suggested by sysconf() 922 buflen = 1024; 923 } 924 std::vector<char> buffer(buflen); 925 gid_t gid = 0; 926 927 auto status = 928 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr); 929 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd. 930 // If no matching password record was found, these functions return 0 931 // and store NULL in *pwdPtr 932 if (!status && (&pwd == pwdPtr)) 933 { 934 gid = pwd.pw_gid; 935 } 936 else 937 { 938 log<level::ERR>("User does not exist", 939 entry("USER_NAME=%s", userName.c_str())); 940 elog<UserNameDoesNotExist>(); 941 } 942 943 struct group *groups = nullptr; 944 std::string ldapGroupName; 945 946 while ((groups = getgrent()) != NULL) 947 { 948 if (groups->gr_gid == gid) 949 { 950 ldapGroupName = groups->gr_name; 951 break; 952 } 953 } 954 // Call endgrent() to close the group database. 955 endgrent(); 956 957 return ldapGroupName; 958 } 959 960 std::string UserMgr::getServiceName(std::string &&path, std::string &&intf) 961 { 962 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath, 963 objMapperInterface, "GetObject"); 964 965 mapperCall.append(std::move(path)); 966 mapperCall.append(std::vector<std::string>({std::move(intf)})); 967 968 auto mapperResponseMsg = bus.call(mapperCall); 969 970 if (mapperResponseMsg.is_method_error()) 971 { 972 log<level::ERR>("Error in mapper call"); 973 elog<InternalFailure>(); 974 } 975 976 std::map<std::string, std::vector<std::string>> mapperResponse; 977 mapperResponseMsg.read(mapperResponse); 978 979 if (mapperResponse.begin() == mapperResponse.end()) 980 { 981 log<level::ERR>("Invalid response from mapper"); 982 elog<InternalFailure>(); 983 } 984 985 return mapperResponse.begin()->first; 986 } 987 988 UserInfoMap UserMgr::getUserInfo(std::string userName) 989 { 990 UserInfoMap userInfo; 991 // Check whether the given user is local user or not. 992 if (isUserExist(userName) == true) 993 { 994 const auto &user = usersList[userName]; 995 userInfo.emplace("UserPrivilege", user.get()->userPrivilege()); 996 userInfo.emplace("UserGroups", user.get()->userGroups()); 997 userInfo.emplace("UserEnabled", user.get()->userEnabled()); 998 userInfo.emplace("UserLockedForFailedAttempt", 999 user.get()->userLockedForFailedAttempt()); 1000 userInfo.emplace("UserPasswordExpired", 1001 user.get()->userPasswordExpired()); 1002 userInfo.emplace("RemoteUser", false); 1003 } 1004 else 1005 { 1006 std::string ldapGroupName = getLdapGroupName(userName); 1007 if (ldapGroupName.empty()) 1008 { 1009 log<level::ERR>("Unable to get group name", 1010 entry("USER_NAME=%s", userName.c_str())); 1011 elog<InternalFailure>(); 1012 } 1013 1014 DbusUserObj objects = getPrivilegeMapperObject(); 1015 1016 std::string privilege; 1017 std::string groupName; 1018 std::string ldapConfigPath; 1019 1020 try 1021 { 1022 for (const auto &obj : objects) 1023 { 1024 for (const auto &interface : obj.second) 1025 { 1026 if ((interface.first == 1027 "xyz.openbmc_project.Object.Enable")) 1028 { 1029 for (const auto &property : interface.second) 1030 { 1031 auto value = 1032 sdbusplus::message::variant_ns::get<bool>( 1033 property.second); 1034 if ((property.first == "Enabled") && 1035 (value == true)) 1036 { 1037 ldapConfigPath = obj.first; 1038 break; 1039 } 1040 } 1041 } 1042 } 1043 if (!ldapConfigPath.empty()) 1044 { 1045 break; 1046 } 1047 } 1048 1049 if (ldapConfigPath.empty()) 1050 { 1051 return userInfo; 1052 } 1053 1054 for (const auto &obj : objects) 1055 { 1056 for (const auto &interface : obj.second) 1057 { 1058 if ((interface.first == 1059 "xyz.openbmc_project.User.PrivilegeMapperEntry") && 1060 (obj.first.str.find(ldapConfigPath) != 1061 std::string::npos)) 1062 { 1063 1064 for (const auto &property : interface.second) 1065 { 1066 auto value = sdbusplus::message::variant_ns::get< 1067 std::string>(property.second); 1068 if (property.first == "GroupName") 1069 { 1070 groupName = value; 1071 } 1072 else if (property.first == "Privilege") 1073 { 1074 privilege = value; 1075 } 1076 if (groupName == ldapGroupName) 1077 { 1078 userInfo["UserPrivilege"] = privilege; 1079 } 1080 } 1081 } 1082 } 1083 } 1084 auto priv = std::get<std::string>(userInfo["UserPrivilege"]); 1085 1086 if (priv.empty()) 1087 { 1088 log<level::ERR>("LDAP group privilege mapping does not exist"); 1089 } 1090 } 1091 catch (const std::bad_variant_access &e) 1092 { 1093 log<level::ERR>("Error while accessing variant", 1094 entry("WHAT=%s", e.what())); 1095 elog<InternalFailure>(); 1096 } 1097 userInfo.emplace("RemoteUser", true); 1098 } 1099 1100 return userInfo; 1101 } 1102 1103 void UserMgr::initUserObjects(void) 1104 { 1105 // All user management lock has to be based on /etc/shadow 1106 phosphor::user::shadow::Lock lock(); 1107 std::vector<std::string> userNameList; 1108 std::vector<std::string> sshGrpUsersList; 1109 UserSSHLists userSSHLists = getUserAndSshGrpList(); 1110 userNameList = std::move(userSSHLists.first); 1111 sshGrpUsersList = std::move(userSSHLists.second); 1112 1113 if (!userNameList.empty()) 1114 { 1115 std::map<std::string, std::vector<std::string>> groupLists; 1116 for (auto &grp : groupsMgr) 1117 { 1118 if (grp == grpSsh) 1119 { 1120 groupLists.emplace(grp, sshGrpUsersList); 1121 } 1122 else 1123 { 1124 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1125 groupLists.emplace(grp, grpUsersList); 1126 } 1127 } 1128 for (auto &grp : privMgr) 1129 { 1130 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1131 groupLists.emplace(grp, grpUsersList); 1132 } 1133 1134 for (auto &user : userNameList) 1135 { 1136 std::vector<std::string> userGroups; 1137 std::string userPriv; 1138 for (const auto &grp : groupLists) 1139 { 1140 std::vector<std::string> tempGrp = grp.second; 1141 if (std::find(tempGrp.begin(), tempGrp.end(), user) != 1142 tempGrp.end()) 1143 { 1144 if (std::find(privMgr.begin(), privMgr.end(), grp.first) != 1145 privMgr.end()) 1146 { 1147 userPriv = grp.first; 1148 } 1149 else 1150 { 1151 userGroups.emplace_back(grp.first); 1152 } 1153 } 1154 } 1155 // Add user objects to the Users path. 1156 auto objPath = std::string(usersObjPath) + "/" + user; 1157 std::sort(userGroups.begin(), userGroups.end()); 1158 usersList.emplace(user, 1159 std::move(std::make_unique<phosphor::user::Users>( 1160 bus, objPath.c_str(), userGroups, userPriv, 1161 isUserEnabled(user), *this))); 1162 } 1163 } 1164 } 1165 1166 UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) : 1167 Ifaces(bus, path, true), bus(bus), path(path) 1168 { 1169 UserMgrIface::allPrivileges(privMgr); 1170 std::sort(groupsMgr.begin(), groupsMgr.end()); 1171 UserMgrIface::allGroups(groupsMgr); 1172 std::string valueStr; 1173 auto value = minPasswdLength; 1174 unsigned long tmp = 0; 1175 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) != 1176 success) 1177 { 1178 AccountPolicyIface::minPasswordLength(minPasswdLength); 1179 } 1180 else 1181 { 1182 try 1183 { 1184 tmp = std::stoul(valueStr, nullptr); 1185 if (tmp > std::numeric_limits<decltype(value)>::max()) 1186 { 1187 throw std::out_of_range("Out of range"); 1188 } 1189 value = static_cast<decltype(value)>(tmp); 1190 } 1191 catch (const std::exception &e) 1192 { 1193 log<level::ERR>("Exception for MinPasswordLength", 1194 entry("WHAT=%s", e.what())); 1195 throw; 1196 } 1197 AccountPolicyIface::minPasswordLength(value); 1198 } 1199 valueStr.clear(); 1200 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) != 1201 success) 1202 { 1203 AccountPolicyIface::rememberOldPasswordTimes(0); 1204 } 1205 else 1206 { 1207 value = 0; 1208 try 1209 { 1210 tmp = std::stoul(valueStr, nullptr); 1211 if (tmp > std::numeric_limits<decltype(value)>::max()) 1212 { 1213 throw std::out_of_range("Out of range"); 1214 } 1215 value = static_cast<decltype(value)>(tmp); 1216 } 1217 catch (const std::exception &e) 1218 { 1219 log<level::ERR>("Exception for RememberOldPasswordTimes", 1220 entry("WHAT=%s", e.what())); 1221 throw; 1222 } 1223 AccountPolicyIface::rememberOldPasswordTimes(value); 1224 } 1225 valueStr.clear(); 1226 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success) 1227 { 1228 AccountPolicyIface::maxLoginAttemptBeforeLockout(0); 1229 } 1230 else 1231 { 1232 uint16_t value16 = 0; 1233 try 1234 { 1235 tmp = std::stoul(valueStr, nullptr); 1236 if (tmp > std::numeric_limits<decltype(value16)>::max()) 1237 { 1238 throw std::out_of_range("Out of range"); 1239 } 1240 value16 = static_cast<decltype(value16)>(tmp); 1241 } 1242 catch (const std::exception &e) 1243 { 1244 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout", 1245 entry("WHAT=%s", e.what())); 1246 throw; 1247 } 1248 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16); 1249 } 1250 valueStr.clear(); 1251 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success) 1252 { 1253 AccountPolicyIface::accountUnlockTimeout(0); 1254 } 1255 else 1256 { 1257 uint32_t value32 = 0; 1258 try 1259 { 1260 tmp = std::stoul(valueStr, nullptr); 1261 if (tmp > std::numeric_limits<decltype(value32)>::max()) 1262 { 1263 throw std::out_of_range("Out of range"); 1264 } 1265 value32 = static_cast<decltype(value32)>(tmp); 1266 } 1267 catch (const std::exception &e) 1268 { 1269 log<level::ERR>("Exception for AccountUnlockTimeout", 1270 entry("WHAT=%s", e.what())); 1271 throw; 1272 } 1273 AccountPolicyIface::accountUnlockTimeout(value32); 1274 } 1275 initUserObjects(); 1276 1277 // emit the signal 1278 this->emit_object_added(); 1279 } 1280 1281 } // namespace user 1282 } // namespace phosphor 1283