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=%s", 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 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on 324 // 1970-01-01, that's an implementation-defined behavior 325 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(), 326 "-m", "-N", "-s", 327 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e", 328 (enabled ? "" : "1970-01-01")); 329 } 330 catch (const InternalFailure& e) 331 { 332 log<level::ERR>("Unable to create new user"); 333 elog<InternalFailure>(); 334 } 335 336 // Add the users object before sending out the signal 337 sdbusplus::message::object_path tempObjPath(usersObjPath); 338 tempObjPath /= userName; 339 std::string userObj(tempObjPath); 340 std::sort(groupNames.begin(), groupNames.end()); 341 usersList.emplace( 342 userName, std::move(std::make_unique<phosphor::user::Users>( 343 bus, userObj.c_str(), groupNames, priv, enabled, *this))); 344 345 log<level::INFO>("User created successfully", 346 entry("USER_NAME=%s", userName.c_str())); 347 return; 348 } 349 350 void UserMgr::deleteUser(std::string userName) 351 { 352 // All user management lock has to be based on /etc/shadow 353 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 354 throwForUserDoesNotExist(userName); 355 try 356 { 357 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r"); 358 } 359 catch (const InternalFailure& e) 360 { 361 log<level::ERR>("User delete failed", 362 entry("USER_NAME=%s", userName.c_str())); 363 elog<InternalFailure>(); 364 } 365 366 usersList.erase(userName); 367 368 log<level::INFO>("User deleted successfully", 369 entry("USER_NAME=%s", userName.c_str())); 370 return; 371 } 372 373 void UserMgr::renameUser(std::string userName, std::string newUserName) 374 { 375 // All user management lock has to be based on /etc/shadow 376 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 377 throwForUserDoesNotExist(userName); 378 throwForUserExists(newUserName); 379 throwForUserNameConstraints(newUserName, 380 usersList[userName].get()->userGroups()); 381 try 382 { 383 std::string newHomeDir = "/home/" + newUserName; 384 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(), 385 userName.c_str(), "-d", newHomeDir.c_str(), "-m"); 386 } 387 catch (const InternalFailure& e) 388 { 389 log<level::ERR>("User rename failed", 390 entry("USER_NAME=%s", userName.c_str())); 391 elog<InternalFailure>(); 392 } 393 const auto& user = usersList[userName]; 394 std::string priv = user.get()->userPrivilege(); 395 std::vector<std::string> groupNames = user.get()->userGroups(); 396 bool enabled = user.get()->userEnabled(); 397 sdbusplus::message::object_path tempObjPath(usersObjPath); 398 tempObjPath /= newUserName; 399 std::string newUserObj(tempObjPath); 400 // Special group 'ipmi' needs a way to identify user renamed, in order to 401 // update encrypted password. It can't rely only on InterfacesRemoved & 402 // InterfacesAdded. So first send out userRenamed signal. 403 this->userRenamed(userName, newUserName); 404 usersList.erase(userName); 405 usersList.emplace( 406 newUserName, 407 std::move(std::make_unique<phosphor::user::Users>( 408 bus, newUserObj.c_str(), groupNames, priv, enabled, *this))); 409 return; 410 } 411 412 void UserMgr::updateGroupsAndPriv(const std::string& userName, 413 const std::vector<std::string>& groupNames, 414 const std::string& priv) 415 { 416 throwForInvalidPrivilege(priv); 417 throwForInvalidGroups(groupNames); 418 // All user management lock has to be based on /etc/shadow 419 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 420 throwForUserDoesNotExist(userName); 421 const std::vector<std::string>& oldGroupNames = 422 usersList[userName].get()->userGroups(); 423 std::vector<std::string> groupDiff; 424 // Note: already dealing with sorted group lists. 425 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(), 426 groupNames.begin(), groupNames.end(), 427 std::back_inserter(groupDiff)); 428 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") != 429 groupDiff.end()) 430 { 431 throwForUserNameConstraints(userName, groupNames); 432 throwForMaxGrpUserCount(groupNames); 433 } 434 435 std::string groups = getCSVFromVector(groupNames); 436 bool sshRequested = removeStringFromCSV(groups, grpSsh); 437 438 // treat privilege as a group - This is to avoid using different file to 439 // store the same. 440 if (!priv.empty()) 441 { 442 if (groups.size() != 0) 443 { 444 groups += ","; 445 } 446 groups += priv; 447 } 448 try 449 { 450 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(), 451 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin")); 452 } 453 catch (const InternalFailure& e) 454 { 455 log<level::ERR>("Unable to modify user privilege / groups"); 456 elog<InternalFailure>(); 457 } 458 459 log<level::INFO>("User groups / privilege updated successfully", 460 entry("USER_NAME=%s", userName.c_str())); 461 return; 462 } 463 464 uint8_t UserMgr::minPasswordLength(uint8_t value) 465 { 466 if (value == AccountPolicyIface::minPasswordLength()) 467 { 468 return value; 469 } 470 if (value < minPasswdLength) 471 { 472 return value; 473 } 474 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp, 475 std::to_string(value)) != success) 476 { 477 log<level::ERR>("Unable to set minPasswordLength"); 478 elog<InternalFailure>(); 479 } 480 return AccountPolicyIface::minPasswordLength(value); 481 } 482 483 uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value) 484 { 485 if (value == AccountPolicyIface::rememberOldPasswordTimes()) 486 { 487 return value; 488 } 489 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount, 490 std::to_string(value)) != success) 491 { 492 log<level::ERR>("Unable to set rememberOldPasswordTimes"); 493 elog<InternalFailure>(); 494 } 495 return AccountPolicyIface::rememberOldPasswordTimes(value); 496 } 497 498 uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value) 499 { 500 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout()) 501 { 502 return value; 503 } 504 if (setPamModuleArgValue(pamTally2, maxFailedAttempt, 505 std::to_string(value)) != success) 506 { 507 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout"); 508 elog<InternalFailure>(); 509 } 510 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value); 511 } 512 513 uint32_t UserMgr::accountUnlockTimeout(uint32_t value) 514 { 515 if (value == AccountPolicyIface::accountUnlockTimeout()) 516 { 517 return value; 518 } 519 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) != 520 success) 521 { 522 log<level::ERR>("Unable to set accountUnlockTimeout"); 523 elog<InternalFailure>(); 524 } 525 return AccountPolicyIface::accountUnlockTimeout(value); 526 } 527 528 int UserMgr::getPamModuleArgValue(const std::string& moduleName, 529 const std::string& argName, 530 std::string& argValue) 531 { 532 std::string fileName; 533 if (moduleName == pamTally2) 534 { 535 fileName = pamAuthConfigFile; 536 } 537 else 538 { 539 fileName = pamPasswdConfigFile; 540 } 541 std::ifstream fileToRead(fileName, std::ios::in); 542 if (!fileToRead.is_open()) 543 { 544 log<level::ERR>("Failed to open pam configuration file", 545 entry("FILE_NAME=%s", fileName.c_str())); 546 return failure; 547 } 548 std::string line; 549 auto argSearch = argName + "="; 550 size_t startPos = 0; 551 size_t endPos = 0; 552 while (getline(fileToRead, line)) 553 { 554 // skip comments section starting with # 555 if ((startPos = line.find('#')) != std::string::npos) 556 { 557 if (startPos == 0) 558 { 559 continue; 560 } 561 // skip comments after meaningful section and process those 562 line = line.substr(0, startPos); 563 } 564 if (line.find(moduleName) != std::string::npos) 565 { 566 if ((startPos = line.find(argSearch)) != std::string::npos) 567 { 568 if ((endPos = line.find(' ', startPos)) == std::string::npos) 569 { 570 endPos = line.size(); 571 } 572 startPos += argSearch.size(); 573 argValue = line.substr(startPos, endPos - startPos); 574 return success; 575 } 576 } 577 } 578 return failure; 579 } 580 581 int UserMgr::setPamModuleArgValue(const std::string& moduleName, 582 const std::string& argName, 583 const std::string& argValue) 584 { 585 std::string fileName; 586 if (moduleName == pamTally2) 587 { 588 fileName = pamAuthConfigFile; 589 } 590 else 591 { 592 fileName = pamPasswdConfigFile; 593 } 594 std::string tmpFileName = fileName + "_tmp"; 595 std::ifstream fileToRead(fileName, std::ios::in); 596 std::ofstream fileToWrite(tmpFileName, std::ios::out); 597 if (!fileToRead.is_open() || !fileToWrite.is_open()) 598 { 599 log<level::ERR>("Failed to open pam configuration /tmp file", 600 entry("FILE_NAME=%s", fileName.c_str())); 601 return failure; 602 } 603 std::string line; 604 auto argSearch = argName + "="; 605 size_t startPos = 0; 606 size_t endPos = 0; 607 bool found = false; 608 while (getline(fileToRead, line)) 609 { 610 // skip comments section starting with # 611 if ((startPos = line.find('#')) != std::string::npos) 612 { 613 if (startPos == 0) 614 { 615 fileToWrite << line << std::endl; 616 continue; 617 } 618 // skip comments after meaningful section and process those 619 line = line.substr(0, startPos); 620 } 621 if (line.find(moduleName) != std::string::npos) 622 { 623 if ((startPos = line.find(argSearch)) != std::string::npos) 624 { 625 if ((endPos = line.find(' ', startPos)) == std::string::npos) 626 { 627 endPos = line.size(); 628 } 629 startPos += argSearch.size(); 630 fileToWrite << line.substr(0, startPos) << argValue 631 << line.substr(endPos, line.size() - endPos) 632 << std::endl; 633 found = true; 634 continue; 635 } 636 } 637 fileToWrite << line << std::endl; 638 } 639 fileToWrite.close(); 640 fileToRead.close(); 641 if (found) 642 { 643 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0) 644 { 645 return success; 646 } 647 } 648 return failure; 649 } 650 651 void UserMgr::userEnable(const std::string& userName, bool enabled) 652 { 653 // All user management lock has to be based on /etc/shadow 654 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 655 throwForUserDoesNotExist(userName); 656 try 657 { 658 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on 659 // 1970-01-01, that's an implementation-defined behavior 660 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e", 661 (enabled ? "" : "1970-01-01")); 662 } 663 catch (const InternalFailure& e) 664 { 665 log<level::ERR>("Unable to modify user enabled state"); 666 elog<InternalFailure>(); 667 } 668 669 log<level::INFO>("User enabled/disabled state updated successfully", 670 entry("USER_NAME=%s", userName.c_str()), 671 entry("ENABLED=%d", enabled)); 672 return; 673 } 674 675 /** 676 * pam_tally2 app will provide the user failure count and failure status 677 * in second line of output with words position [0] - user name, 678 * [1] - failure count, [2] - latest timestamp, [3] - failure timestamp 679 * [4] - failure app 680 **/ 681 682 static constexpr size_t t2UserIdx = 0; 683 static constexpr size_t t2FailCntIdx = 1; 684 static constexpr size_t t2OutputIndex = 1; 685 686 bool UserMgr::userLockedForFailedAttempt(const std::string& userName) 687 { 688 // All user management lock has to be based on /etc/shadow 689 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 690 std::vector<std::string> output; 691 692 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str()); 693 694 std::vector<std::string> splitWords; 695 boost::algorithm::split(splitWords, output[t2OutputIndex], 696 boost::algorithm::is_any_of("\t "), 697 boost::token_compress_on); 698 699 try 700 { 701 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr); 702 uint16_t value16 = 0; 703 if (tmp > std::numeric_limits<decltype(value16)>::max()) 704 { 705 throw std::out_of_range("Out of range"); 706 } 707 value16 = static_cast<decltype(value16)>(tmp); 708 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 && 709 value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout()) 710 { 711 return true; // User account is locked out 712 } 713 return false; // User account is un-locked 714 } 715 catch (const std::exception& e) 716 { 717 log<level::ERR>("Exception for userLockedForFailedAttempt", 718 entry("WHAT=%s", e.what())); 719 throw; 720 } 721 } 722 723 bool UserMgr::userLockedForFailedAttempt(const std::string& userName, 724 const bool& value) 725 { 726 // All user management lock has to be based on /etc/shadow 727 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 728 std::vector<std::string> output; 729 if (value == true) 730 { 731 return userLockedForFailedAttempt(userName); 732 } 733 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r"); 734 735 std::vector<std::string> splitWords; 736 boost::algorithm::split(splitWords, output[t2OutputIndex], 737 boost::algorithm::is_any_of("\t "), 738 boost::token_compress_on); 739 740 return userLockedForFailedAttempt(userName); 741 } 742 743 bool UserMgr::userPasswordExpired(const std::string& userName) 744 { 745 // All user management lock has to be based on /etc/shadow 746 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 747 748 struct spwd spwd 749 {}; 750 struct spwd* spwdPtr = nullptr; 751 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); 752 if (buflen < -1) 753 { 754 // Use a default size if there is no hard limit suggested by sysconf() 755 buflen = 1024; 756 } 757 std::vector<char> buffer(buflen); 758 auto status = 759 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr); 760 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd. 761 // If no matching password record was found, these functions return 0 762 // and store NULL in *spwdPtr 763 if ((status == 0) && (&spwd == spwdPtr)) 764 { 765 // Determine password validity per "chage" docs, where: 766 // spwd.sp_lstchg == 0 means password is expired, and 767 // spwd.sp_max == -1 means the password does not expire. 768 constexpr long seconds_per_day = 60 * 60 * 24; 769 long today = static_cast<long>(time(NULL)) / seconds_per_day; 770 if ((spwd.sp_lstchg == 0) || 771 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today))) 772 { 773 return true; 774 } 775 } 776 else 777 { 778 // User entry is missing in /etc/shadow, indicating no SHA password. 779 // Treat this as new user without password entry in /etc/shadow 780 // TODO: Add property to indicate user password was not set yet 781 // https://github.com/openbmc/phosphor-user-manager/issues/8 782 return false; 783 } 784 785 return false; 786 } 787 788 UserSSHLists UserMgr::getUserAndSshGrpList() 789 { 790 // All user management lock has to be based on /etc/shadow 791 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 792 793 std::vector<std::string> userList; 794 std::vector<std::string> sshUsersList; 795 struct passwd pw, *pwp = nullptr; 796 std::array<char, 1024> buffer{}; 797 798 phosphor::user::File passwd(passwdFileName, "r"); 799 if ((passwd)() == NULL) 800 { 801 log<level::ERR>("Error opening the passwd file"); 802 elog<InternalFailure>(); 803 } 804 805 while (true) 806 { 807 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(), 808 &pwp); 809 if ((r != 0) || (pwp == NULL)) 810 { 811 // Any error, break the loop. 812 break; 813 } 814 #ifdef ENABLE_ROOT_USER_MGMT 815 // Add all users whose UID >= 1000 and < 65534 816 // and special UID 0. 817 if ((pwp->pw_uid == 0) || 818 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))) 819 #else 820 // Add all users whose UID >=1000 and < 65534 821 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)) 822 #endif 823 { 824 std::string userName(pwp->pw_name); 825 userList.emplace_back(userName); 826 827 // ssh doesn't have separate group. Check login shell entry to 828 // get all users list which are member of ssh group. 829 std::string loginShell(pwp->pw_shell); 830 if (loginShell == "/bin/sh") 831 { 832 sshUsersList.emplace_back(userName); 833 } 834 } 835 } 836 endpwent(); 837 return std::make_pair(std::move(userList), std::move(sshUsersList)); 838 } 839 840 size_t UserMgr::getIpmiUsersCount() 841 { 842 std::vector<std::string> userList = getUsersInGroup("ipmi"); 843 return userList.size(); 844 } 845 846 bool UserMgr::isUserEnabled(const std::string& userName) 847 { 848 // All user management lock has to be based on /etc/shadow 849 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 850 std::array<char, 4096> buffer{}; 851 struct spwd spwd; 852 struct spwd* resultPtr = nullptr; 853 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(), 854 buffer.max_size(), &resultPtr); 855 if (!status && (&spwd == resultPtr)) 856 { 857 if (resultPtr->sp_expire >= 0) 858 { 859 return false; // user locked out 860 } 861 return true; 862 } 863 return false; // assume user is disabled for any error. 864 } 865 866 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName) 867 { 868 std::vector<std::string> usersInGroup; 869 // Should be more than enough to get the pwd structure. 870 std::array<char, 4096> buffer{}; 871 struct group grp; 872 struct group* resultPtr = nullptr; 873 874 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(), 875 buffer.max_size(), &resultPtr); 876 877 if (!status && (&grp == resultPtr)) 878 { 879 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem)) 880 { 881 usersInGroup.emplace_back(*(grp.gr_mem)); 882 } 883 } 884 else 885 { 886 log<level::ERR>("Group not found", 887 entry("GROUP=%s", groupName.c_str())); 888 // Don't throw error, just return empty userList - fallback 889 } 890 return usersInGroup; 891 } 892 893 DbusUserObj UserMgr::getPrivilegeMapperObject(void) 894 { 895 DbusUserObj objects; 896 try 897 { 898 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap"; 899 std::string interface = "xyz.openbmc_project.User.Ldap.Config"; 900 901 auto ldapMgmtService = 902 getServiceName(std::move(basePath), std::move(interface)); 903 auto method = bus.new_method_call( 904 ldapMgmtService.c_str(), ldapMgrObjBasePath, 905 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 906 907 auto reply = bus.call(method); 908 reply.read(objects); 909 } 910 catch (const InternalFailure& e) 911 { 912 log<level::ERR>("Unable to get the User Service", 913 entry("WHAT=%s", e.what())); 914 throw; 915 } 916 catch (const sdbusplus::exception::exception& e) 917 { 918 log<level::ERR>( 919 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"), 920 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what())); 921 throw; 922 } 923 return objects; 924 } 925 926 std::string UserMgr::getLdapGroupName(const std::string& userName) 927 { 928 struct passwd pwd 929 {}; 930 struct passwd* pwdPtr = nullptr; 931 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); 932 if (buflen < -1) 933 { 934 // Use a default size if there is no hard limit suggested by sysconf() 935 buflen = 1024; 936 } 937 std::vector<char> buffer(buflen); 938 gid_t gid = 0; 939 940 auto status = 941 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr); 942 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd. 943 // If no matching password record was found, these functions return 0 944 // and store NULL in *pwdPtr 945 if (!status && (&pwd == pwdPtr)) 946 { 947 gid = pwd.pw_gid; 948 } 949 else 950 { 951 log<level::ERR>("User does not exist", 952 entry("USER_NAME=%s", userName.c_str())); 953 elog<UserNameDoesNotExist>(); 954 } 955 956 struct group* groups = nullptr; 957 std::string ldapGroupName; 958 959 while ((groups = getgrent()) != NULL) 960 { 961 if (groups->gr_gid == gid) 962 { 963 ldapGroupName = groups->gr_name; 964 break; 965 } 966 } 967 // Call endgrent() to close the group database. 968 endgrent(); 969 970 return ldapGroupName; 971 } 972 973 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf) 974 { 975 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath, 976 objMapperInterface, "GetObject"); 977 978 mapperCall.append(std::move(path)); 979 mapperCall.append(std::vector<std::string>({std::move(intf)})); 980 981 auto mapperResponseMsg = bus.call(mapperCall); 982 983 if (mapperResponseMsg.is_method_error()) 984 { 985 log<level::ERR>("Error in mapper call"); 986 elog<InternalFailure>(); 987 } 988 989 std::map<std::string, std::vector<std::string>> mapperResponse; 990 mapperResponseMsg.read(mapperResponse); 991 992 if (mapperResponse.begin() == mapperResponse.end()) 993 { 994 log<level::ERR>("Invalid response from mapper"); 995 elog<InternalFailure>(); 996 } 997 998 return mapperResponse.begin()->first; 999 } 1000 1001 UserInfoMap UserMgr::getUserInfo(std::string userName) 1002 { 1003 UserInfoMap userInfo; 1004 // Check whether the given user is local user or not. 1005 if (isUserExist(userName) == true) 1006 { 1007 const auto& user = usersList[userName]; 1008 userInfo.emplace("UserPrivilege", user.get()->userPrivilege()); 1009 userInfo.emplace("UserGroups", user.get()->userGroups()); 1010 userInfo.emplace("UserEnabled", user.get()->userEnabled()); 1011 userInfo.emplace("UserLockedForFailedAttempt", 1012 user.get()->userLockedForFailedAttempt()); 1013 userInfo.emplace("UserPasswordExpired", 1014 user.get()->userPasswordExpired()); 1015 userInfo.emplace("RemoteUser", false); 1016 } 1017 else 1018 { 1019 std::string ldapGroupName = getLdapGroupName(userName); 1020 if (ldapGroupName.empty()) 1021 { 1022 log<level::ERR>("Unable to get group name", 1023 entry("USER_NAME=%s", userName.c_str())); 1024 elog<InternalFailure>(); 1025 } 1026 1027 DbusUserObj objects = getPrivilegeMapperObject(); 1028 1029 std::string privilege; 1030 std::string groupName; 1031 std::string ldapConfigPath; 1032 1033 try 1034 { 1035 for (const auto& obj : objects) 1036 { 1037 for (const auto& interface : obj.second) 1038 { 1039 if ((interface.first == 1040 "xyz.openbmc_project.Object.Enable")) 1041 { 1042 for (const auto& property : interface.second) 1043 { 1044 auto value = std::get<bool>(property.second); 1045 if ((property.first == "Enabled") && 1046 (value == true)) 1047 { 1048 ldapConfigPath = obj.first; 1049 break; 1050 } 1051 } 1052 } 1053 } 1054 if (!ldapConfigPath.empty()) 1055 { 1056 break; 1057 } 1058 } 1059 1060 if (ldapConfigPath.empty()) 1061 { 1062 return userInfo; 1063 } 1064 1065 for (const auto& obj : objects) 1066 { 1067 for (const auto& interface : obj.second) 1068 { 1069 if ((interface.first == 1070 "xyz.openbmc_project.User.PrivilegeMapperEntry") && 1071 (obj.first.str.find(ldapConfigPath) != 1072 std::string::npos)) 1073 { 1074 1075 for (const auto& property : interface.second) 1076 { 1077 auto value = std::get<std::string>(property.second); 1078 if (property.first == "GroupName") 1079 { 1080 groupName = value; 1081 } 1082 else if (property.first == "Privilege") 1083 { 1084 privilege = value; 1085 } 1086 if (groupName == ldapGroupName) 1087 { 1088 userInfo["UserPrivilege"] = privilege; 1089 } 1090 } 1091 } 1092 } 1093 } 1094 auto priv = std::get<std::string>(userInfo["UserPrivilege"]); 1095 1096 if (priv.empty()) 1097 { 1098 log<level::ERR>("LDAP group privilege mapping does not exist"); 1099 } 1100 } 1101 catch (const std::bad_variant_access& e) 1102 { 1103 log<level::ERR>("Error while accessing variant", 1104 entry("WHAT=%s", e.what())); 1105 elog<InternalFailure>(); 1106 } 1107 userInfo.emplace("RemoteUser", true); 1108 } 1109 1110 return userInfo; 1111 } 1112 1113 void UserMgr::initUserObjects(void) 1114 { 1115 // All user management lock has to be based on /etc/shadow 1116 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 1117 std::vector<std::string> userNameList; 1118 std::vector<std::string> sshGrpUsersList; 1119 UserSSHLists userSSHLists = getUserAndSshGrpList(); 1120 userNameList = std::move(userSSHLists.first); 1121 sshGrpUsersList = std::move(userSSHLists.second); 1122 1123 if (!userNameList.empty()) 1124 { 1125 std::map<std::string, std::vector<std::string>> groupLists; 1126 for (auto& grp : groupsMgr) 1127 { 1128 if (grp == grpSsh) 1129 { 1130 groupLists.emplace(grp, sshGrpUsersList); 1131 } 1132 else 1133 { 1134 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1135 groupLists.emplace(grp, grpUsersList); 1136 } 1137 } 1138 for (auto& grp : privMgr) 1139 { 1140 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1141 groupLists.emplace(grp, grpUsersList); 1142 } 1143 1144 for (auto& user : userNameList) 1145 { 1146 std::vector<std::string> userGroups; 1147 std::string userPriv; 1148 for (const auto& grp : groupLists) 1149 { 1150 std::vector<std::string> tempGrp = grp.second; 1151 if (std::find(tempGrp.begin(), tempGrp.end(), user) != 1152 tempGrp.end()) 1153 { 1154 if (std::find(privMgr.begin(), privMgr.end(), grp.first) != 1155 privMgr.end()) 1156 { 1157 userPriv = grp.first; 1158 } 1159 else 1160 { 1161 userGroups.emplace_back(grp.first); 1162 } 1163 } 1164 } 1165 // Add user objects to the Users path. 1166 sdbusplus::message::object_path tempObjPath(usersObjPath); 1167 tempObjPath /= user; 1168 std::string objPath(tempObjPath); 1169 std::sort(userGroups.begin(), userGroups.end()); 1170 usersList.emplace(user, 1171 std::move(std::make_unique<phosphor::user::Users>( 1172 bus, objPath.c_str(), userGroups, userPriv, 1173 isUserEnabled(user), *this))); 1174 } 1175 } 1176 } 1177 1178 UserMgr::UserMgr(sdbusplus::bus::bus& bus, const char* path) : 1179 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path) 1180 { 1181 UserMgrIface::allPrivileges(privMgr); 1182 std::sort(groupsMgr.begin(), groupsMgr.end()); 1183 UserMgrIface::allGroups(groupsMgr); 1184 std::string valueStr; 1185 auto value = minPasswdLength; 1186 unsigned long tmp = 0; 1187 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) != 1188 success) 1189 { 1190 AccountPolicyIface::minPasswordLength(minPasswdLength); 1191 } 1192 else 1193 { 1194 try 1195 { 1196 tmp = std::stoul(valueStr, nullptr); 1197 if (tmp > std::numeric_limits<decltype(value)>::max()) 1198 { 1199 throw std::out_of_range("Out of range"); 1200 } 1201 value = static_cast<decltype(value)>(tmp); 1202 } 1203 catch (const std::exception& e) 1204 { 1205 log<level::ERR>("Exception for MinPasswordLength", 1206 entry("WHAT=%s", e.what())); 1207 throw; 1208 } 1209 AccountPolicyIface::minPasswordLength(value); 1210 } 1211 valueStr.clear(); 1212 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) != 1213 success) 1214 { 1215 AccountPolicyIface::rememberOldPasswordTimes(0); 1216 } 1217 else 1218 { 1219 value = 0; 1220 try 1221 { 1222 tmp = std::stoul(valueStr, nullptr); 1223 if (tmp > std::numeric_limits<decltype(value)>::max()) 1224 { 1225 throw std::out_of_range("Out of range"); 1226 } 1227 value = static_cast<decltype(value)>(tmp); 1228 } 1229 catch (const std::exception& e) 1230 { 1231 log<level::ERR>("Exception for RememberOldPasswordTimes", 1232 entry("WHAT=%s", e.what())); 1233 throw; 1234 } 1235 AccountPolicyIface::rememberOldPasswordTimes(value); 1236 } 1237 valueStr.clear(); 1238 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success) 1239 { 1240 AccountPolicyIface::maxLoginAttemptBeforeLockout(0); 1241 } 1242 else 1243 { 1244 uint16_t value16 = 0; 1245 try 1246 { 1247 tmp = std::stoul(valueStr, nullptr); 1248 if (tmp > std::numeric_limits<decltype(value16)>::max()) 1249 { 1250 throw std::out_of_range("Out of range"); 1251 } 1252 value16 = static_cast<decltype(value16)>(tmp); 1253 } 1254 catch (const std::exception& e) 1255 { 1256 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout", 1257 entry("WHAT=%s", e.what())); 1258 throw; 1259 } 1260 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16); 1261 } 1262 valueStr.clear(); 1263 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success) 1264 { 1265 AccountPolicyIface::accountUnlockTimeout(0); 1266 } 1267 else 1268 { 1269 uint32_t value32 = 0; 1270 try 1271 { 1272 tmp = std::stoul(valueStr, nullptr); 1273 if (tmp > std::numeric_limits<decltype(value32)>::max()) 1274 { 1275 throw std::out_of_range("Out of range"); 1276 } 1277 value32 = static_cast<decltype(value32)>(tmp); 1278 } 1279 catch (const std::exception& e) 1280 { 1281 log<level::ERR>("Exception for AccountUnlockTimeout", 1282 entry("WHAT=%s", e.what())); 1283 throw; 1284 } 1285 AccountPolicyIface::accountUnlockTimeout(value32); 1286 } 1287 initUserObjects(); 1288 1289 // emit the signal 1290 this->emit_object_added(); 1291 } 1292 1293 } // namespace user 1294 } // namespace phosphor 1295