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