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