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