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 t2FailCntIdx = 1; 690 static constexpr size_t t2FailDateIdx = 2; 691 static constexpr size_t t2FailTimeIdx = 3; 692 static constexpr size_t t2OutputIndex = 1; 693 694 bool UserMgr::userLockedForFailedAttempt(const std::string& userName) 695 { 696 // All user management lock has to be based on /etc/shadow 697 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 698 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0) 699 { 700 return false; 701 } 702 703 std::vector<std::string> output; 704 try 705 { 706 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str()); 707 } 708 catch (const InternalFailure& e) 709 { 710 log<level::ERR>("Unable to read login failure counter"); 711 elog<InternalFailure>(); 712 } 713 714 std::vector<std::string> splitWords; 715 boost::algorithm::split(splitWords, output[t2OutputIndex], 716 boost::algorithm::is_any_of("\t "), 717 boost::token_compress_on); 718 719 uint16_t failAttempts = 0; 720 try 721 { 722 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr); 723 if (tmp > std::numeric_limits<decltype(failAttempts)>::max()) 724 { 725 throw std::out_of_range("Out of range"); 726 } 727 failAttempts = static_cast<decltype(failAttempts)>(tmp); 728 } 729 catch (const std::exception& e) 730 { 731 log<level::ERR>("Exception for userLockedForFailedAttempt", 732 entry("WHAT=%s", e.what())); 733 elog<InternalFailure>(); 734 } 735 736 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout()) 737 { 738 return false; 739 } 740 741 // When failedAttempts is not 0, Latest failure date/time should be 742 // available 743 if (splitWords.size() < 4) 744 { 745 log<level::ERR>("Unable to read latest failure date/time"); 746 elog<InternalFailure>(); 747 } 748 749 const std::string failDateTime = 750 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx]; 751 752 // NOTE: Cannot use std::get_time() here as the implementation of %y in 753 // libstdc++ does not match POSIX strptime() before gcc 12.1.0 754 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888 755 std::tm tmStruct = {}; 756 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct)) 757 { 758 log<level::ERR>("Failed to parse latest failure date/time"); 759 elog<InternalFailure>(); 760 } 761 762 time_t failTimestamp = std::mktime(&tmStruct); 763 if (failTimestamp + 764 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <= 765 std::time(NULL)) 766 { 767 return false; 768 } 769 770 return true; 771 } 772 773 bool UserMgr::userLockedForFailedAttempt(const std::string& userName, 774 const bool& value) 775 { 776 // All user management lock has to be based on /etc/shadow 777 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 778 if (value == true) 779 { 780 return userLockedForFailedAttempt(userName); 781 } 782 783 try 784 { 785 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r"); 786 } 787 catch (const InternalFailure& e) 788 { 789 log<level::ERR>("Unable to reset login failure counter"); 790 elog<InternalFailure>(); 791 } 792 793 return userLockedForFailedAttempt(userName); 794 } 795 796 bool UserMgr::userPasswordExpired(const std::string& userName) 797 { 798 // All user management lock has to be based on /etc/shadow 799 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 800 801 struct spwd spwd 802 {}; 803 struct spwd* spwdPtr = nullptr; 804 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); 805 if (buflen < -1) 806 { 807 // Use a default size if there is no hard limit suggested by sysconf() 808 buflen = 1024; 809 } 810 std::vector<char> buffer(buflen); 811 auto status = 812 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr); 813 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd. 814 // If no matching password record was found, these functions return 0 815 // and store NULL in *spwdPtr 816 if ((status == 0) && (&spwd == spwdPtr)) 817 { 818 // Determine password validity per "chage" docs, where: 819 // spwd.sp_lstchg == 0 means password is expired, and 820 // spwd.sp_max == -1 means the password does not expire. 821 constexpr long seconds_per_day = 60 * 60 * 24; 822 long today = static_cast<long>(time(NULL)) / seconds_per_day; 823 if ((spwd.sp_lstchg == 0) || 824 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today))) 825 { 826 return true; 827 } 828 } 829 else 830 { 831 // User entry is missing in /etc/shadow, indicating no SHA password. 832 // Treat this as new user without password entry in /etc/shadow 833 // TODO: Add property to indicate user password was not set yet 834 // https://github.com/openbmc/phosphor-user-manager/issues/8 835 return false; 836 } 837 838 return false; 839 } 840 841 UserSSHLists UserMgr::getUserAndSshGrpList() 842 { 843 // All user management lock has to be based on /etc/shadow 844 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 845 846 std::vector<std::string> userList; 847 std::vector<std::string> sshUsersList; 848 struct passwd pw, *pwp = nullptr; 849 std::array<char, 1024> buffer{}; 850 851 phosphor::user::File passwd(passwdFileName, "r"); 852 if ((passwd)() == NULL) 853 { 854 log<level::ERR>("Error opening the passwd file"); 855 elog<InternalFailure>(); 856 } 857 858 while (true) 859 { 860 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(), 861 &pwp); 862 if ((r != 0) || (pwp == NULL)) 863 { 864 // Any error, break the loop. 865 break; 866 } 867 #ifdef ENABLE_ROOT_USER_MGMT 868 // Add all users whose UID >= 1000 and < 65534 869 // and special UID 0. 870 if ((pwp->pw_uid == 0) || 871 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))) 872 #else 873 // Add all users whose UID >=1000 and < 65534 874 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)) 875 #endif 876 { 877 std::string userName(pwp->pw_name); 878 userList.emplace_back(userName); 879 880 // ssh doesn't have separate group. Check login shell entry to 881 // get all users list which are member of ssh group. 882 std::string loginShell(pwp->pw_shell); 883 if (loginShell == "/bin/sh") 884 { 885 sshUsersList.emplace_back(userName); 886 } 887 } 888 } 889 endpwent(); 890 return std::make_pair(std::move(userList), std::move(sshUsersList)); 891 } 892 893 size_t UserMgr::getIpmiUsersCount() 894 { 895 std::vector<std::string> userList = getUsersInGroup("ipmi"); 896 return userList.size(); 897 } 898 899 bool UserMgr::isUserEnabled(const std::string& userName) 900 { 901 // All user management lock has to be based on /etc/shadow 902 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 903 std::array<char, 4096> buffer{}; 904 struct spwd spwd; 905 struct spwd* resultPtr = nullptr; 906 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(), 907 buffer.max_size(), &resultPtr); 908 if (!status && (&spwd == resultPtr)) 909 { 910 if (resultPtr->sp_expire >= 0) 911 { 912 return false; // user locked out 913 } 914 return true; 915 } 916 return false; // assume user is disabled for any error. 917 } 918 919 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName) 920 { 921 std::vector<std::string> usersInGroup; 922 // Should be more than enough to get the pwd structure. 923 std::array<char, 4096> buffer{}; 924 struct group grp; 925 struct group* resultPtr = nullptr; 926 927 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(), 928 buffer.max_size(), &resultPtr); 929 930 if (!status && (&grp == resultPtr)) 931 { 932 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem)) 933 { 934 usersInGroup.emplace_back(*(grp.gr_mem)); 935 } 936 } 937 else 938 { 939 log<level::ERR>("Group not found", 940 entry("GROUP=%s", groupName.c_str())); 941 // Don't throw error, just return empty userList - fallback 942 } 943 return usersInGroup; 944 } 945 946 DbusUserObj UserMgr::getPrivilegeMapperObject(void) 947 { 948 DbusUserObj objects; 949 try 950 { 951 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap"; 952 std::string interface = "xyz.openbmc_project.User.Ldap.Config"; 953 954 auto ldapMgmtService = 955 getServiceName(std::move(basePath), std::move(interface)); 956 auto method = bus.new_method_call( 957 ldapMgmtService.c_str(), ldapMgrObjBasePath, 958 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 959 960 auto reply = bus.call(method); 961 reply.read(objects); 962 } 963 catch (const InternalFailure& e) 964 { 965 log<level::ERR>("Unable to get the User Service", 966 entry("WHAT=%s", e.what())); 967 throw; 968 } 969 catch (const sdbusplus::exception_t& e) 970 { 971 log<level::ERR>( 972 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"), 973 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what())); 974 throw; 975 } 976 return objects; 977 } 978 979 std::string UserMgr::getLdapGroupName(const std::string& userName) 980 { 981 struct passwd pwd 982 {}; 983 struct passwd* pwdPtr = nullptr; 984 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); 985 if (buflen < -1) 986 { 987 // Use a default size if there is no hard limit suggested by sysconf() 988 buflen = 1024; 989 } 990 std::vector<char> buffer(buflen); 991 gid_t gid = 0; 992 993 auto status = 994 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr); 995 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd. 996 // If no matching password record was found, these functions return 0 997 // and store NULL in *pwdPtr 998 if (!status && (&pwd == pwdPtr)) 999 { 1000 gid = pwd.pw_gid; 1001 } 1002 else 1003 { 1004 log<level::ERR>("User does not exist", 1005 entry("USER_NAME=%s", userName.c_str())); 1006 elog<UserNameDoesNotExist>(); 1007 } 1008 1009 struct group* groups = nullptr; 1010 std::string ldapGroupName; 1011 1012 while ((groups = getgrent()) != NULL) 1013 { 1014 if (groups->gr_gid == gid) 1015 { 1016 ldapGroupName = groups->gr_name; 1017 break; 1018 } 1019 } 1020 // Call endgrent() to close the group database. 1021 endgrent(); 1022 1023 return ldapGroupName; 1024 } 1025 1026 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf) 1027 { 1028 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath, 1029 objMapperInterface, "GetObject"); 1030 1031 mapperCall.append(std::move(path)); 1032 mapperCall.append(std::vector<std::string>({std::move(intf)})); 1033 1034 auto mapperResponseMsg = bus.call(mapperCall); 1035 1036 if (mapperResponseMsg.is_method_error()) 1037 { 1038 log<level::ERR>("Error in mapper call"); 1039 elog<InternalFailure>(); 1040 } 1041 1042 std::map<std::string, std::vector<std::string>> mapperResponse; 1043 mapperResponseMsg.read(mapperResponse); 1044 1045 if (mapperResponse.begin() == mapperResponse.end()) 1046 { 1047 log<level::ERR>("Invalid response from mapper"); 1048 elog<InternalFailure>(); 1049 } 1050 1051 return mapperResponse.begin()->first; 1052 } 1053 1054 UserInfoMap UserMgr::getUserInfo(std::string userName) 1055 { 1056 UserInfoMap userInfo; 1057 // Check whether the given user is local user or not. 1058 if (isUserExist(userName) == true) 1059 { 1060 const auto& user = usersList[userName]; 1061 userInfo.emplace("UserPrivilege", user.get()->userPrivilege()); 1062 userInfo.emplace("UserGroups", user.get()->userGroups()); 1063 userInfo.emplace("UserEnabled", user.get()->userEnabled()); 1064 userInfo.emplace("UserLockedForFailedAttempt", 1065 user.get()->userLockedForFailedAttempt()); 1066 userInfo.emplace("UserPasswordExpired", 1067 user.get()->userPasswordExpired()); 1068 userInfo.emplace("RemoteUser", false); 1069 } 1070 else 1071 { 1072 std::string ldapGroupName = getLdapGroupName(userName); 1073 if (ldapGroupName.empty()) 1074 { 1075 log<level::ERR>("Unable to get group name", 1076 entry("USER_NAME=%s", userName.c_str())); 1077 elog<InternalFailure>(); 1078 } 1079 1080 DbusUserObj objects = getPrivilegeMapperObject(); 1081 1082 std::string ldapConfigPath; 1083 std::string userPrivilege; 1084 1085 try 1086 { 1087 for (const auto& obj : objects) 1088 { 1089 for (const auto& interface : obj.second) 1090 { 1091 if ((interface.first == 1092 "xyz.openbmc_project.Object.Enable")) 1093 { 1094 for (const auto& property : interface.second) 1095 { 1096 auto value = std::get<bool>(property.second); 1097 if ((property.first == "Enabled") && 1098 (value == true)) 1099 { 1100 ldapConfigPath = obj.first; 1101 break; 1102 } 1103 } 1104 } 1105 } 1106 if (!ldapConfigPath.empty()) 1107 { 1108 break; 1109 } 1110 } 1111 1112 if (ldapConfigPath.empty()) 1113 { 1114 return userInfo; 1115 } 1116 1117 for (const auto& obj : objects) 1118 { 1119 for (const auto& interface : obj.second) 1120 { 1121 if ((interface.first == 1122 "xyz.openbmc_project.User.PrivilegeMapperEntry") && 1123 (obj.first.str.find(ldapConfigPath) != 1124 std::string::npos)) 1125 { 1126 std::string privilege; 1127 std::string groupName; 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 } 1141 if (groupName == ldapGroupName) 1142 { 1143 userPrivilege = privilege; 1144 break; 1145 } 1146 } 1147 } 1148 if (!userPrivilege.empty()) 1149 { 1150 break; 1151 } 1152 } 1153 1154 if (userPrivilege.empty()) 1155 { 1156 log<level::ERR>("LDAP group privilege mapping does not exist"); 1157 } 1158 userInfo.emplace("UserPrivilege", userPrivilege); 1159 } 1160 catch (const std::bad_variant_access& e) 1161 { 1162 log<level::ERR>("Error while accessing variant", 1163 entry("WHAT=%s", e.what())); 1164 elog<InternalFailure>(); 1165 } 1166 userInfo.emplace("RemoteUser", true); 1167 } 1168 1169 return userInfo; 1170 } 1171 1172 void UserMgr::initUserObjects(void) 1173 { 1174 // All user management lock has to be based on /etc/shadow 1175 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 1176 std::vector<std::string> userNameList; 1177 std::vector<std::string> sshGrpUsersList; 1178 UserSSHLists userSSHLists = getUserAndSshGrpList(); 1179 userNameList = std::move(userSSHLists.first); 1180 sshGrpUsersList = std::move(userSSHLists.second); 1181 1182 if (!userNameList.empty()) 1183 { 1184 std::map<std::string, std::vector<std::string>> groupLists; 1185 for (auto& grp : groupsMgr) 1186 { 1187 if (grp == grpSsh) 1188 { 1189 groupLists.emplace(grp, sshGrpUsersList); 1190 } 1191 else 1192 { 1193 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1194 groupLists.emplace(grp, grpUsersList); 1195 } 1196 } 1197 for (auto& grp : privMgr) 1198 { 1199 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1200 groupLists.emplace(grp, grpUsersList); 1201 } 1202 1203 for (auto& user : userNameList) 1204 { 1205 std::vector<std::string> userGroups; 1206 std::string userPriv; 1207 for (const auto& grp : groupLists) 1208 { 1209 std::vector<std::string> tempGrp = grp.second; 1210 if (std::find(tempGrp.begin(), tempGrp.end(), user) != 1211 tempGrp.end()) 1212 { 1213 if (std::find(privMgr.begin(), privMgr.end(), grp.first) != 1214 privMgr.end()) 1215 { 1216 userPriv = grp.first; 1217 } 1218 else 1219 { 1220 userGroups.emplace_back(grp.first); 1221 } 1222 } 1223 } 1224 // Add user objects to the Users path. 1225 sdbusplus::message::object_path tempObjPath(usersObjPath); 1226 tempObjPath /= user; 1227 std::string objPath(tempObjPath); 1228 std::sort(userGroups.begin(), userGroups.end()); 1229 usersList.emplace(user, 1230 std::move(std::make_unique<phosphor::user::Users>( 1231 bus, objPath.c_str(), userGroups, userPriv, 1232 isUserEnabled(user), *this))); 1233 } 1234 } 1235 } 1236 1237 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) : 1238 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path) 1239 { 1240 UserMgrIface::allPrivileges(privMgr); 1241 std::sort(groupsMgr.begin(), groupsMgr.end()); 1242 UserMgrIface::allGroups(groupsMgr); 1243 std::string valueStr; 1244 auto value = minPasswdLength; 1245 unsigned long tmp = 0; 1246 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) != 1247 success) 1248 { 1249 AccountPolicyIface::minPasswordLength(minPasswdLength); 1250 } 1251 else 1252 { 1253 try 1254 { 1255 tmp = std::stoul(valueStr, nullptr); 1256 if (tmp > std::numeric_limits<decltype(value)>::max()) 1257 { 1258 throw std::out_of_range("Out of range"); 1259 } 1260 value = static_cast<decltype(value)>(tmp); 1261 } 1262 catch (const std::exception& e) 1263 { 1264 log<level::ERR>("Exception for MinPasswordLength", 1265 entry("WHAT=%s", e.what())); 1266 throw; 1267 } 1268 AccountPolicyIface::minPasswordLength(value); 1269 } 1270 valueStr.clear(); 1271 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) != 1272 success) 1273 { 1274 AccountPolicyIface::rememberOldPasswordTimes(0); 1275 } 1276 else 1277 { 1278 value = 0; 1279 try 1280 { 1281 tmp = std::stoul(valueStr, nullptr); 1282 if (tmp > std::numeric_limits<decltype(value)>::max()) 1283 { 1284 throw std::out_of_range("Out of range"); 1285 } 1286 value = static_cast<decltype(value)>(tmp); 1287 } 1288 catch (const std::exception& e) 1289 { 1290 log<level::ERR>("Exception for RememberOldPasswordTimes", 1291 entry("WHAT=%s", e.what())); 1292 throw; 1293 } 1294 AccountPolicyIface::rememberOldPasswordTimes(value); 1295 } 1296 valueStr.clear(); 1297 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success) 1298 { 1299 AccountPolicyIface::maxLoginAttemptBeforeLockout(0); 1300 } 1301 else 1302 { 1303 uint16_t value16 = 0; 1304 try 1305 { 1306 tmp = std::stoul(valueStr, nullptr); 1307 if (tmp > std::numeric_limits<decltype(value16)>::max()) 1308 { 1309 throw std::out_of_range("Out of range"); 1310 } 1311 value16 = static_cast<decltype(value16)>(tmp); 1312 } 1313 catch (const std::exception& e) 1314 { 1315 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout", 1316 entry("WHAT=%s", e.what())); 1317 throw; 1318 } 1319 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16); 1320 } 1321 valueStr.clear(); 1322 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success) 1323 { 1324 AccountPolicyIface::accountUnlockTimeout(0); 1325 } 1326 else 1327 { 1328 uint32_t value32 = 0; 1329 try 1330 { 1331 tmp = std::stoul(valueStr, nullptr); 1332 if (tmp > std::numeric_limits<decltype(value32)>::max()) 1333 { 1334 throw std::out_of_range("Out of range"); 1335 } 1336 value32 = static_cast<decltype(value32)>(tmp); 1337 } 1338 catch (const std::exception& e) 1339 { 1340 log<level::ERR>("Exception for AccountUnlockTimeout", 1341 entry("WHAT=%s", e.what())); 1342 throw; 1343 } 1344 AccountPolicyIface::accountUnlockTimeout(value32); 1345 } 1346 initUserObjects(); 1347 1348 // emit the signal 1349 this->emit_object_added(); 1350 } 1351 1352 } // namespace user 1353 } // namespace phosphor 1354