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::getLdapGroupName(const std::string& userName) 939 { 940 struct passwd pwd 941 {}; 942 struct passwd* pwdPtr = nullptr; 943 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); 944 if (buflen < -1) 945 { 946 // Use a default size if there is no hard limit suggested by sysconf() 947 buflen = 1024; 948 } 949 std::vector<char> buffer(buflen); 950 gid_t gid = 0; 951 952 auto status = 953 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr); 954 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd. 955 // If no matching password record was found, these functions return 0 956 // and store NULL in *pwdPtr 957 if (!status && (&pwd == pwdPtr)) 958 { 959 gid = pwd.pw_gid; 960 } 961 else 962 { 963 log<level::ERR>("User does not exist", 964 entry("USER_NAME=%s", userName.c_str())); 965 elog<UserNameDoesNotExist>(); 966 } 967 968 struct group* groups = nullptr; 969 std::string ldapGroupName; 970 971 while ((groups = getgrent()) != NULL) 972 { 973 if (groups->gr_gid == gid) 974 { 975 ldapGroupName = groups->gr_name; 976 break; 977 } 978 } 979 // Call endgrent() to close the group database. 980 endgrent(); 981 982 return ldapGroupName; 983 } 984 985 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf) 986 { 987 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath, 988 objMapperInterface, "GetObject"); 989 990 mapperCall.append(std::move(path)); 991 mapperCall.append(std::vector<std::string>({std::move(intf)})); 992 993 auto mapperResponseMsg = bus.call(mapperCall); 994 995 if (mapperResponseMsg.is_method_error()) 996 { 997 log<level::ERR>("Error in mapper call"); 998 elog<InternalFailure>(); 999 } 1000 1001 std::map<std::string, std::vector<std::string>> mapperResponse; 1002 mapperResponseMsg.read(mapperResponse); 1003 1004 if (mapperResponse.begin() == mapperResponse.end()) 1005 { 1006 log<level::ERR>("Invalid response from mapper"); 1007 elog<InternalFailure>(); 1008 } 1009 1010 return mapperResponse.begin()->first; 1011 } 1012 1013 UserInfoMap UserMgr::getUserInfo(std::string userName) 1014 { 1015 UserInfoMap userInfo; 1016 // Check whether the given user is local user or not. 1017 if (isUserExist(userName)) 1018 { 1019 const auto& user = usersList[userName]; 1020 userInfo.emplace("UserPrivilege", user.get()->userPrivilege()); 1021 userInfo.emplace("UserGroups", user.get()->userGroups()); 1022 userInfo.emplace("UserEnabled", user.get()->userEnabled()); 1023 userInfo.emplace("UserLockedForFailedAttempt", 1024 user.get()->userLockedForFailedAttempt()); 1025 userInfo.emplace("UserPasswordExpired", 1026 user.get()->userPasswordExpired()); 1027 userInfo.emplace("RemoteUser", false); 1028 } 1029 else 1030 { 1031 std::string ldapGroupName = getLdapGroupName(userName); 1032 if (ldapGroupName.empty()) 1033 { 1034 log<level::ERR>("Unable to get group name", 1035 entry("USER_NAME=%s", userName.c_str())); 1036 elog<InternalFailure>(); 1037 } 1038 1039 DbusUserObj objects = getPrivilegeMapperObject(); 1040 1041 std::string ldapConfigPath; 1042 std::string userPrivilege; 1043 1044 try 1045 { 1046 for (const auto& obj : objects) 1047 { 1048 for (const auto& interface : obj.second) 1049 { 1050 if ((interface.first == 1051 "xyz.openbmc_project.Object.Enable")) 1052 { 1053 for (const auto& property : interface.second) 1054 { 1055 auto value = std::get<bool>(property.second); 1056 if ((property.first == "Enabled") && 1057 (value == true)) 1058 { 1059 ldapConfigPath = obj.first; 1060 break; 1061 } 1062 } 1063 } 1064 } 1065 if (!ldapConfigPath.empty()) 1066 { 1067 break; 1068 } 1069 } 1070 1071 if (ldapConfigPath.empty()) 1072 { 1073 return userInfo; 1074 } 1075 1076 for (const auto& obj : objects) 1077 { 1078 for (const auto& interface : obj.second) 1079 { 1080 if ((interface.first == 1081 "xyz.openbmc_project.User.PrivilegeMapperEntry") && 1082 (obj.first.str.find(ldapConfigPath) != 1083 std::string::npos)) 1084 { 1085 std::string privilege; 1086 std::string groupName; 1087 1088 for (const auto& property : interface.second) 1089 { 1090 auto value = std::get<std::string>(property.second); 1091 if (property.first == "GroupName") 1092 { 1093 groupName = value; 1094 } 1095 else if (property.first == "Privilege") 1096 { 1097 privilege = value; 1098 } 1099 } 1100 if (groupName == ldapGroupName) 1101 { 1102 userPrivilege = privilege; 1103 break; 1104 } 1105 } 1106 } 1107 if (!userPrivilege.empty()) 1108 { 1109 break; 1110 } 1111 } 1112 1113 if (userPrivilege.empty()) 1114 { 1115 log<level::ERR>("LDAP group privilege mapping does not exist"); 1116 } 1117 userInfo.emplace("UserPrivilege", userPrivilege); 1118 } 1119 catch (const std::bad_variant_access& e) 1120 { 1121 log<level::ERR>("Error while accessing variant", 1122 entry("WHAT=%s", e.what())); 1123 elog<InternalFailure>(); 1124 } 1125 userInfo.emplace("RemoteUser", true); 1126 } 1127 1128 return userInfo; 1129 } 1130 1131 void UserMgr::initializeAccountPolicy() 1132 { 1133 std::string valueStr; 1134 auto value = minPasswdLength; 1135 unsigned long tmp = 0; 1136 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) != 1137 success) 1138 { 1139 AccountPolicyIface::minPasswordLength(minPasswdLength); 1140 } 1141 else 1142 { 1143 try 1144 { 1145 tmp = std::stoul(valueStr, nullptr); 1146 if (tmp > std::numeric_limits<decltype(value)>::max()) 1147 { 1148 throw std::out_of_range("Out of range"); 1149 } 1150 value = static_cast<decltype(value)>(tmp); 1151 } 1152 catch (const std::exception& e) 1153 { 1154 log<level::ERR>("Exception for MinPasswordLength", 1155 entry("WHAT=%s", e.what())); 1156 throw; 1157 } 1158 AccountPolicyIface::minPasswordLength(value); 1159 } 1160 valueStr.clear(); 1161 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) != 1162 success) 1163 { 1164 AccountPolicyIface::rememberOldPasswordTimes(0); 1165 } 1166 else 1167 { 1168 value = 0; 1169 try 1170 { 1171 tmp = std::stoul(valueStr, nullptr); 1172 if (tmp > std::numeric_limits<decltype(value)>::max()) 1173 { 1174 throw std::out_of_range("Out of range"); 1175 } 1176 value = static_cast<decltype(value)>(tmp); 1177 } 1178 catch (const std::exception& e) 1179 { 1180 log<level::ERR>("Exception for RememberOldPasswordTimes", 1181 entry("WHAT=%s", e.what())); 1182 throw; 1183 } 1184 AccountPolicyIface::rememberOldPasswordTimes(value); 1185 } 1186 valueStr.clear(); 1187 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success) 1188 { 1189 AccountPolicyIface::maxLoginAttemptBeforeLockout(0); 1190 } 1191 else 1192 { 1193 uint16_t value16 = 0; 1194 try 1195 { 1196 tmp = std::stoul(valueStr, nullptr); 1197 if (tmp > std::numeric_limits<decltype(value16)>::max()) 1198 { 1199 throw std::out_of_range("Out of range"); 1200 } 1201 value16 = static_cast<decltype(value16)>(tmp); 1202 } 1203 catch (const std::exception& e) 1204 { 1205 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout", 1206 entry("WHAT=%s", e.what())); 1207 throw; 1208 } 1209 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16); 1210 } 1211 valueStr.clear(); 1212 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success) 1213 { 1214 AccountPolicyIface::accountUnlockTimeout(0); 1215 } 1216 else 1217 { 1218 uint32_t value32 = 0; 1219 try 1220 { 1221 tmp = std::stoul(valueStr, nullptr); 1222 if (tmp > std::numeric_limits<decltype(value32)>::max()) 1223 { 1224 throw std::out_of_range("Out of range"); 1225 } 1226 value32 = static_cast<decltype(value32)>(tmp); 1227 } 1228 catch (const std::exception& e) 1229 { 1230 log<level::ERR>("Exception for AccountUnlockTimeout", 1231 entry("WHAT=%s", e.what())); 1232 throw; 1233 } 1234 AccountPolicyIface::accountUnlockTimeout(value32); 1235 } 1236 } 1237 1238 void UserMgr::initUserObjects(void) 1239 { 1240 // All user management lock has to be based on /etc/shadow 1241 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 1242 std::vector<std::string> userNameList; 1243 std::vector<std::string> sshGrpUsersList; 1244 UserSSHLists userSSHLists = getUserAndSshGrpList(); 1245 userNameList = std::move(userSSHLists.first); 1246 sshGrpUsersList = std::move(userSSHLists.second); 1247 1248 if (!userNameList.empty()) 1249 { 1250 std::map<std::string, std::vector<std::string>> groupLists; 1251 for (auto& grp : groupsMgr) 1252 { 1253 if (grp == grpSsh) 1254 { 1255 groupLists.emplace(grp, sshGrpUsersList); 1256 } 1257 else 1258 { 1259 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1260 groupLists.emplace(grp, grpUsersList); 1261 } 1262 } 1263 for (auto& grp : privMgr) 1264 { 1265 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1266 groupLists.emplace(grp, grpUsersList); 1267 } 1268 1269 for (auto& user : userNameList) 1270 { 1271 std::vector<std::string> userGroups; 1272 std::string userPriv; 1273 for (const auto& grp : groupLists) 1274 { 1275 std::vector<std::string> tempGrp = grp.second; 1276 if (std::find(tempGrp.begin(), tempGrp.end(), user) != 1277 tempGrp.end()) 1278 { 1279 if (std::find(privMgr.begin(), privMgr.end(), grp.first) != 1280 privMgr.end()) 1281 { 1282 userPriv = grp.first; 1283 } 1284 else 1285 { 1286 userGroups.emplace_back(grp.first); 1287 } 1288 } 1289 } 1290 // Add user objects to the Users path. 1291 sdbusplus::message::object_path tempObjPath(usersObjPath); 1292 tempObjPath /= user; 1293 std::string objPath(tempObjPath); 1294 std::sort(userGroups.begin(), userGroups.end()); 1295 usersList.emplace(user, std::make_unique<phosphor::user::Users>( 1296 bus, objPath.c_str(), userGroups, 1297 userPriv, isUserEnabled(user), *this)); 1298 } 1299 } 1300 } 1301 1302 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) : 1303 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path), 1304 pamPasswdConfigFile(defaultPamPasswdConfigFile), 1305 pamAuthConfigFile(defaultPamAuthConfigFile) 1306 { 1307 UserMgrIface::allPrivileges(privMgr); 1308 std::sort(groupsMgr.begin(), groupsMgr.end()); 1309 UserMgrIface::allGroups(groupsMgr); 1310 initializeAccountPolicy(); 1311 initUserObjects(); 1312 1313 // emit the signal 1314 this->emit_object_added(); 1315 } 1316 1317 void UserMgr::executeUserAdd(const char* userName, const char* groups, 1318 bool sshRequested, bool enabled) 1319 { 1320 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on 1321 // 1970-01-01, that's an implementation-defined behavior 1322 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s", 1323 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e", 1324 (enabled ? "" : "1970-01-01")); 1325 } 1326 1327 void UserMgr::executeUserDelete(const char* userName) 1328 { 1329 executeCmd("/usr/sbin/userdel", userName, "-r"); 1330 } 1331 1332 void UserMgr::executeUserRename(const char* userName, const char* newUserName) 1333 { 1334 std::string newHomeDir = "/home/"; 1335 newHomeDir += newUserName; 1336 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d", 1337 newHomeDir.c_str(), "-m"); 1338 } 1339 1340 void UserMgr::executeUserModify(const char* userName, const char* newGroups, 1341 bool sshRequested) 1342 { 1343 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s", 1344 (sshRequested ? "/bin/sh" : "/bin/nologin")); 1345 } 1346 1347 void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled) 1348 { 1349 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on 1350 // 1970-01-01, that's an implementation-defined behavior 1351 executeCmd("/usr/sbin/usermod", userName, "-e", 1352 (enabled ? "" : "1970-01-01")); 1353 } 1354 1355 std::vector<std::string> UserMgr::getFailedAttempt(const char* userName) 1356 { 1357 return executeCmd("/usr/sbin/pam_tally2", "-u", userName); 1358 } 1359 1360 void UserMgr::createGroup(std::string /*groupName*/) 1361 { 1362 log<level::ERR>("Not implemented yet"); 1363 elog<InternalFailure>(); 1364 } 1365 1366 void UserMgr::deleteGroup(std::string /*groupName*/) 1367 { 1368 log<level::ERR>("Not implemented yet"); 1369 elog<InternalFailure>(); 1370 } 1371 1372 } // namespace user 1373 } // namespace phosphor 1374