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