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 <fstream> 44 #include <numeric> 45 #include <regex> 46 47 namespace phosphor 48 { 49 namespace user 50 { 51 52 static constexpr const char* passwdFileName = "/etc/passwd"; 53 static constexpr size_t ipmiMaxUsers = 15; 54 static constexpr size_t ipmiMaxUserNameLen = 16; 55 static constexpr size_t systemMaxUserNameLen = 30; 56 static constexpr size_t maxSystemUsers = 30; 57 static constexpr const char* grpSsh = "ssh"; 58 static constexpr uint8_t minPasswdLength = 8; 59 static constexpr int success = 0; 60 static constexpr int failure = -1; 61 62 // pam modules related 63 static constexpr const char* pamTally2 = "pam_tally2.so"; 64 static constexpr const char* pamCrackLib = "pam_cracklib.so"; 65 static constexpr const char* pamPWHistory = "pam_pwhistory.so"; 66 static constexpr const char* minPasswdLenProp = "minlen"; 67 static constexpr const char* remOldPasswdCount = "remember"; 68 static constexpr const char* maxFailedAttempt = "deny"; 69 static constexpr const char* unlockTimeout = "unlock_time"; 70 static constexpr const char* pamPasswdConfigFile = "/etc/pam.d/common-password"; 71 static constexpr const char* pamAuthConfigFile = "/etc/pam.d/common-auth"; 72 73 // Object Manager related 74 static constexpr const char* ldapMgrObjBasePath = 75 "/xyz/openbmc_project/user/ldap"; 76 77 // Object Mapper related 78 static constexpr const char* objMapperService = 79 "xyz.openbmc_project.ObjectMapper"; 80 static constexpr const char* objMapperPath = 81 "/xyz/openbmc_project/object_mapper"; 82 static constexpr const char* objMapperInterface = 83 "xyz.openbmc_project.ObjectMapper"; 84 85 using namespace phosphor::logging; 86 using InsufficientPermission = 87 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission; 88 using InternalFailure = 89 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 90 using InvalidArgument = 91 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; 92 using UserNameExists = 93 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists; 94 using UserNameDoesNotExist = 95 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist; 96 using UserNameGroupFail = 97 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail; 98 using NoResource = 99 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource; 100 101 using Argument = xyz::openbmc_project::Common::InvalidArgument; 102 103 template <typename... ArgTypes> 104 static std::vector<std::string> executeCmd(const char* path, 105 ArgTypes&&... tArgs) 106 { 107 std::vector<std::string> stdOutput; 108 boost::process::ipstream stdOutStream; 109 boost::process::child execProg(path, const_cast<char*>(tArgs)..., 110 boost::process::std_out > stdOutStream); 111 std::string stdOutLine; 112 113 while (stdOutStream && std::getline(stdOutStream, stdOutLine) && 114 !stdOutLine.empty()) 115 { 116 stdOutput.emplace_back(stdOutLine); 117 } 118 119 execProg.wait(); 120 121 int retCode = execProg.exit_code(); 122 if (retCode) 123 { 124 log<level::ERR>("Command execution failed", entry("PATH=%s", path), 125 entry("RETURN_CODE=%d", retCode)); 126 elog<InternalFailure>(); 127 } 128 129 return stdOutput; 130 } 131 132 static std::string getCSVFromVector(std::vector<std::string> vec) 133 { 134 switch (vec.size()) 135 { 136 case 0: 137 { 138 return ""; 139 } 140 break; 141 142 case 1: 143 { 144 return std::string{vec[0]}; 145 } 146 break; 147 148 default: 149 { 150 return std::accumulate( 151 std::next(vec.begin()), vec.end(), vec[0], 152 [](std::string a, std::string b) { return a + ',' + b; }); 153 } 154 } 155 } 156 157 static bool removeStringFromCSV(std::string& csvStr, const std::string& delStr) 158 { 159 std::string::size_type delStrPos = csvStr.find(delStr); 160 if (delStrPos != std::string::npos) 161 { 162 // need to also delete the comma char 163 if (delStrPos == 0) 164 { 165 csvStr.erase(delStrPos, delStr.size() + 1); 166 } 167 else 168 { 169 csvStr.erase(delStrPos - 1, delStr.size() + 1); 170 } 171 return true; 172 } 173 return false; 174 } 175 176 bool UserMgr::isUserExist(const std::string& userName) 177 { 178 if (userName.empty()) 179 { 180 log<level::ERR>("User name is empty"); 181 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 182 Argument::ARGUMENT_VALUE("Null")); 183 } 184 if (usersList.find(userName) == usersList.end()) 185 { 186 return false; 187 } 188 return true; 189 } 190 191 void UserMgr::throwForUserDoesNotExist(const std::string& userName) 192 { 193 if (isUserExist(userName) == false) 194 { 195 log<level::ERR>("User does not exist", 196 entry("USER_NAME=%s", userName.c_str())); 197 elog<UserNameDoesNotExist>(); 198 } 199 } 200 201 void UserMgr::throwForUserExists(const std::string& userName) 202 { 203 if (isUserExist(userName) == true) 204 { 205 log<level::ERR>("User already exists", 206 entry("USER_NAME=%s", userName.c_str())); 207 elog<UserNameExists>(); 208 } 209 } 210 211 void UserMgr::throwForUserNameConstraints( 212 const std::string& userName, const std::vector<std::string>& groupNames) 213 { 214 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != 215 groupNames.end()) 216 { 217 if (userName.length() > ipmiMaxUserNameLen) 218 { 219 log<level::ERR>("IPMI user name length limitation", 220 entry("SIZE=%d", userName.length())); 221 elog<UserNameGroupFail>( 222 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON( 223 "IPMI length")); 224 } 225 } 226 if (userName.length() > systemMaxUserNameLen) 227 { 228 log<level::ERR>("User name length limitation", 229 entry("SIZE=%d", userName.length())); 230 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 231 Argument::ARGUMENT_VALUE("Invalid length")); 232 } 233 if (!std::regex_match(userName.c_str(), 234 std::regex("[a-zA-z_][a-zA-Z_0-9]*"))) 235 { 236 log<level::ERR>("Invalid user name", 237 entry("USER_NAME=%s", userName.c_str())); 238 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 239 Argument::ARGUMENT_VALUE("Invalid data")); 240 } 241 } 242 243 void UserMgr::throwForMaxGrpUserCount( 244 const std::vector<std::string>& groupNames) 245 { 246 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != 247 groupNames.end()) 248 { 249 if (getIpmiUsersCount() >= ipmiMaxUsers) 250 { 251 log<level::ERR>("IPMI user limit reached"); 252 elog<NoResource>( 253 xyz::openbmc_project::User::Common::NoResource::REASON( 254 "ipmi user count reached")); 255 } 256 } 257 else 258 { 259 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >= 260 (maxSystemUsers - ipmiMaxUsers)) 261 { 262 log<level::ERR>("Non-ipmi User limit reached"); 263 elog<NoResource>( 264 xyz::openbmc_project::User::Common::NoResource::REASON( 265 "Non-ipmi user count reached")); 266 } 267 } 268 return; 269 } 270 271 void UserMgr::throwForInvalidPrivilege(const std::string& priv) 272 { 273 if (!priv.empty() && 274 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end())) 275 { 276 log<level::ERR>("Invalid privilege"); 277 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"), 278 Argument::ARGUMENT_VALUE(priv.c_str())); 279 } 280 } 281 282 void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames) 283 { 284 for (auto& group : groupNames) 285 { 286 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) == 287 groupsMgr.end()) 288 { 289 log<level::ERR>("Invalid Group Name listed"); 290 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"), 291 Argument::ARGUMENT_VALUE(group.c_str())); 292 } 293 } 294 } 295 296 void UserMgr::createUser(std::string userName, 297 std::vector<std::string> groupNames, std::string priv, 298 bool enabled) 299 { 300 throwForInvalidPrivilege(priv); 301 throwForInvalidGroups(groupNames); 302 // All user management lock has to be based on /etc/shadow 303 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 304 throwForUserExists(userName); 305 throwForUserNameConstraints(userName, groupNames); 306 throwForMaxGrpUserCount(groupNames); 307 308 std::string groups = getCSVFromVector(groupNames); 309 bool sshRequested = removeStringFromCSV(groups, grpSsh); 310 311 // treat privilege as a group - This is to avoid using different file to 312 // store the same. 313 if (!priv.empty()) 314 { 315 if (groups.size() != 0) 316 { 317 groups += ","; 318 } 319 groups += priv; 320 } 321 try 322 { 323 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on 324 // 1970-01-01, that's an implementation-defined behavior 325 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(), 326 "-m", "-N", "-s", 327 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e", 328 (enabled ? "" : "1970-01-01")); 329 } 330 catch (const InternalFailure& e) 331 { 332 log<level::ERR>("Unable to create new user"); 333 elog<InternalFailure>(); 334 } 335 336 // Add the users object before sending out the signal 337 sdbusplus::message::object_path tempObjPath(usersObjPath); 338 tempObjPath /= userName; 339 std::string userObj(tempObjPath); 340 std::sort(groupNames.begin(), groupNames.end()); 341 usersList.emplace( 342 userName, std::move(std::make_unique<phosphor::user::Users>( 343 bus, userObj.c_str(), groupNames, priv, enabled, *this))); 344 345 log<level::INFO>("User created successfully", 346 entry("USER_NAME=%s", userName.c_str())); 347 return; 348 } 349 350 void UserMgr::deleteUser(std::string userName) 351 { 352 // All user management lock has to be based on /etc/shadow 353 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 354 throwForUserDoesNotExist(userName); 355 try 356 { 357 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r"); 358 } 359 catch (const InternalFailure& e) 360 { 361 log<level::ERR>("User delete failed", 362 entry("USER_NAME=%s", userName.c_str())); 363 elog<InternalFailure>(); 364 } 365 366 usersList.erase(userName); 367 368 log<level::INFO>("User deleted successfully", 369 entry("USER_NAME=%s", userName.c_str())); 370 return; 371 } 372 373 void UserMgr::renameUser(std::string userName, std::string newUserName) 374 { 375 // All user management lock has to be based on /etc/shadow 376 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 377 throwForUserDoesNotExist(userName); 378 throwForUserExists(newUserName); 379 throwForUserNameConstraints(newUserName, 380 usersList[userName].get()->userGroups()); 381 try 382 { 383 std::string newHomeDir = "/home/" + newUserName; 384 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(), 385 userName.c_str(), "-d", newHomeDir.c_str(), "-m"); 386 } 387 catch (const InternalFailure& e) 388 { 389 log<level::ERR>("User rename failed", 390 entry("USER_NAME=%s", userName.c_str())); 391 elog<InternalFailure>(); 392 } 393 const auto& user = usersList[userName]; 394 std::string priv = user.get()->userPrivilege(); 395 std::vector<std::string> groupNames = user.get()->userGroups(); 396 bool enabled = user.get()->userEnabled(); 397 sdbusplus::message::object_path tempObjPath(usersObjPath); 398 tempObjPath /= newUserName; 399 std::string newUserObj(tempObjPath); 400 // Special group 'ipmi' needs a way to identify user renamed, in order to 401 // update encrypted password. It can't rely only on InterfacesRemoved & 402 // InterfacesAdded. So first send out userRenamed signal. 403 this->userRenamed(userName, newUserName); 404 usersList.erase(userName); 405 usersList.emplace( 406 newUserName, 407 std::move(std::make_unique<phosphor::user::Users>( 408 bus, newUserObj.c_str(), groupNames, 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 timestamp, [3] - failure timestamp 685 * [4] - failure app 686 **/ 687 688 static constexpr size_t t2UserIdx = 0; 689 static constexpr size_t t2FailCntIdx = 1; 690 static constexpr size_t t2OutputIndex = 1; 691 692 bool UserMgr::userLockedForFailedAttempt(const std::string& userName) 693 { 694 // All user management lock has to be based on /etc/shadow 695 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 696 std::vector<std::string> output; 697 698 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str()); 699 700 std::vector<std::string> splitWords; 701 boost::algorithm::split(splitWords, output[t2OutputIndex], 702 boost::algorithm::is_any_of("\t "), 703 boost::token_compress_on); 704 705 try 706 { 707 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr); 708 uint16_t value16 = 0; 709 if (tmp > std::numeric_limits<decltype(value16)>::max()) 710 { 711 throw std::out_of_range("Out of range"); 712 } 713 value16 = static_cast<decltype(value16)>(tmp); 714 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 && 715 value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout()) 716 { 717 return true; // User account is locked out 718 } 719 return false; // User account is un-locked 720 } 721 catch (const std::exception& e) 722 { 723 log<level::ERR>("Exception for userLockedForFailedAttempt", 724 entry("WHAT=%s", e.what())); 725 throw; 726 } 727 } 728 729 bool UserMgr::userLockedForFailedAttempt(const std::string& userName, 730 const bool& value) 731 { 732 // All user management lock has to be based on /etc/shadow 733 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 734 std::vector<std::string> output; 735 if (value == true) 736 { 737 return userLockedForFailedAttempt(userName); 738 } 739 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r"); 740 741 std::vector<std::string> splitWords; 742 boost::algorithm::split(splitWords, output[t2OutputIndex], 743 boost::algorithm::is_any_of("\t "), 744 boost::token_compress_on); 745 746 return userLockedForFailedAttempt(userName); 747 } 748 749 bool UserMgr::userPasswordExpired(const std::string& userName) 750 { 751 // All user management lock has to be based on /etc/shadow 752 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 753 754 struct spwd spwd 755 {}; 756 struct spwd* spwdPtr = nullptr; 757 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); 758 if (buflen < -1) 759 { 760 // Use a default size if there is no hard limit suggested by sysconf() 761 buflen = 1024; 762 } 763 std::vector<char> buffer(buflen); 764 auto status = 765 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr); 766 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd. 767 // If no matching password record was found, these functions return 0 768 // and store NULL in *spwdPtr 769 if ((status == 0) && (&spwd == spwdPtr)) 770 { 771 // Determine password validity per "chage" docs, where: 772 // spwd.sp_lstchg == 0 means password is expired, and 773 // spwd.sp_max == -1 means the password does not expire. 774 constexpr long seconds_per_day = 60 * 60 * 24; 775 long today = static_cast<long>(time(NULL)) / seconds_per_day; 776 if ((spwd.sp_lstchg == 0) || 777 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today))) 778 { 779 return true; 780 } 781 } 782 else 783 { 784 // User entry is missing in /etc/shadow, indicating no SHA password. 785 // Treat this as new user without password entry in /etc/shadow 786 // TODO: Add property to indicate user password was not set yet 787 // https://github.com/openbmc/phosphor-user-manager/issues/8 788 return false; 789 } 790 791 return false; 792 } 793 794 UserSSHLists UserMgr::getUserAndSshGrpList() 795 { 796 // All user management lock has to be based on /etc/shadow 797 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 798 799 std::vector<std::string> userList; 800 std::vector<std::string> sshUsersList; 801 struct passwd pw, *pwp = nullptr; 802 std::array<char, 1024> buffer{}; 803 804 phosphor::user::File passwd(passwdFileName, "r"); 805 if ((passwd)() == NULL) 806 { 807 log<level::ERR>("Error opening the passwd file"); 808 elog<InternalFailure>(); 809 } 810 811 while (true) 812 { 813 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(), 814 &pwp); 815 if ((r != 0) || (pwp == NULL)) 816 { 817 // Any error, break the loop. 818 break; 819 } 820 #ifdef ENABLE_ROOT_USER_MGMT 821 // Add all users whose UID >= 1000 and < 65534 822 // and special UID 0. 823 if ((pwp->pw_uid == 0) || 824 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))) 825 #else 826 // Add all users whose UID >=1000 and < 65534 827 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)) 828 #endif 829 { 830 std::string userName(pwp->pw_name); 831 userList.emplace_back(userName); 832 833 // ssh doesn't have separate group. Check login shell entry to 834 // get all users list which are member of ssh group. 835 std::string loginShell(pwp->pw_shell); 836 if (loginShell == "/bin/sh") 837 { 838 sshUsersList.emplace_back(userName); 839 } 840 } 841 } 842 endpwent(); 843 return std::make_pair(std::move(userList), std::move(sshUsersList)); 844 } 845 846 size_t UserMgr::getIpmiUsersCount() 847 { 848 std::vector<std::string> userList = getUsersInGroup("ipmi"); 849 return userList.size(); 850 } 851 852 bool UserMgr::isUserEnabled(const std::string& userName) 853 { 854 // All user management lock has to be based on /etc/shadow 855 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 856 std::array<char, 4096> buffer{}; 857 struct spwd spwd; 858 struct spwd* resultPtr = nullptr; 859 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(), 860 buffer.max_size(), &resultPtr); 861 if (!status && (&spwd == resultPtr)) 862 { 863 if (resultPtr->sp_expire >= 0) 864 { 865 return false; // user locked out 866 } 867 return true; 868 } 869 return false; // assume user is disabled for any error. 870 } 871 872 std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName) 873 { 874 std::vector<std::string> usersInGroup; 875 // Should be more than enough to get the pwd structure. 876 std::array<char, 4096> buffer{}; 877 struct group grp; 878 struct group* resultPtr = nullptr; 879 880 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(), 881 buffer.max_size(), &resultPtr); 882 883 if (!status && (&grp == resultPtr)) 884 { 885 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem)) 886 { 887 usersInGroup.emplace_back(*(grp.gr_mem)); 888 } 889 } 890 else 891 { 892 log<level::ERR>("Group not found", 893 entry("GROUP=%s", groupName.c_str())); 894 // Don't throw error, just return empty userList - fallback 895 } 896 return usersInGroup; 897 } 898 899 DbusUserObj UserMgr::getPrivilegeMapperObject(void) 900 { 901 DbusUserObj objects; 902 try 903 { 904 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap"; 905 std::string interface = "xyz.openbmc_project.User.Ldap.Config"; 906 907 auto ldapMgmtService = 908 getServiceName(std::move(basePath), std::move(interface)); 909 auto method = bus.new_method_call( 910 ldapMgmtService.c_str(), ldapMgrObjBasePath, 911 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 912 913 auto reply = bus.call(method); 914 reply.read(objects); 915 } 916 catch (const InternalFailure& e) 917 { 918 log<level::ERR>("Unable to get the User Service", 919 entry("WHAT=%s", e.what())); 920 throw; 921 } 922 catch (const sdbusplus::exception_t& e) 923 { 924 log<level::ERR>( 925 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"), 926 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what())); 927 throw; 928 } 929 return objects; 930 } 931 932 std::string UserMgr::getLdapGroupName(const std::string& userName) 933 { 934 struct passwd pwd 935 {}; 936 struct passwd* pwdPtr = nullptr; 937 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX); 938 if (buflen < -1) 939 { 940 // Use a default size if there is no hard limit suggested by sysconf() 941 buflen = 1024; 942 } 943 std::vector<char> buffer(buflen); 944 gid_t gid = 0; 945 946 auto status = 947 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr); 948 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd. 949 // If no matching password record was found, these functions return 0 950 // and store NULL in *pwdPtr 951 if (!status && (&pwd == pwdPtr)) 952 { 953 gid = pwd.pw_gid; 954 } 955 else 956 { 957 log<level::ERR>("User does not exist", 958 entry("USER_NAME=%s", userName.c_str())); 959 elog<UserNameDoesNotExist>(); 960 } 961 962 struct group* groups = nullptr; 963 std::string ldapGroupName; 964 965 while ((groups = getgrent()) != NULL) 966 { 967 if (groups->gr_gid == gid) 968 { 969 ldapGroupName = groups->gr_name; 970 break; 971 } 972 } 973 // Call endgrent() to close the group database. 974 endgrent(); 975 976 return ldapGroupName; 977 } 978 979 std::string UserMgr::getServiceName(std::string&& path, std::string&& intf) 980 { 981 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath, 982 objMapperInterface, "GetObject"); 983 984 mapperCall.append(std::move(path)); 985 mapperCall.append(std::vector<std::string>({std::move(intf)})); 986 987 auto mapperResponseMsg = bus.call(mapperCall); 988 989 if (mapperResponseMsg.is_method_error()) 990 { 991 log<level::ERR>("Error in mapper call"); 992 elog<InternalFailure>(); 993 } 994 995 std::map<std::string, std::vector<std::string>> mapperResponse; 996 mapperResponseMsg.read(mapperResponse); 997 998 if (mapperResponse.begin() == mapperResponse.end()) 999 { 1000 log<level::ERR>("Invalid response from mapper"); 1001 elog<InternalFailure>(); 1002 } 1003 1004 return mapperResponse.begin()->first; 1005 } 1006 1007 UserInfoMap UserMgr::getUserInfo(std::string userName) 1008 { 1009 UserInfoMap userInfo; 1010 // Check whether the given user is local user or not. 1011 if (isUserExist(userName) == true) 1012 { 1013 const auto& user = usersList[userName]; 1014 userInfo.emplace("UserPrivilege", user.get()->userPrivilege()); 1015 userInfo.emplace("UserGroups", user.get()->userGroups()); 1016 userInfo.emplace("UserEnabled", user.get()->userEnabled()); 1017 userInfo.emplace("UserLockedForFailedAttempt", 1018 user.get()->userLockedForFailedAttempt()); 1019 userInfo.emplace("UserPasswordExpired", 1020 user.get()->userPasswordExpired()); 1021 userInfo.emplace("RemoteUser", false); 1022 } 1023 else 1024 { 1025 std::string ldapGroupName = getLdapGroupName(userName); 1026 if (ldapGroupName.empty()) 1027 { 1028 log<level::ERR>("Unable to get group name", 1029 entry("USER_NAME=%s", userName.c_str())); 1030 elog<InternalFailure>(); 1031 } 1032 1033 DbusUserObj objects = getPrivilegeMapperObject(); 1034 1035 std::string privilege; 1036 std::string groupName; 1037 std::string ldapConfigPath; 1038 1039 try 1040 { 1041 for (const auto& obj : objects) 1042 { 1043 for (const auto& interface : obj.second) 1044 { 1045 if ((interface.first == 1046 "xyz.openbmc_project.Object.Enable")) 1047 { 1048 for (const auto& property : interface.second) 1049 { 1050 auto value = std::get<bool>(property.second); 1051 if ((property.first == "Enabled") && 1052 (value == true)) 1053 { 1054 ldapConfigPath = obj.first; 1055 break; 1056 } 1057 } 1058 } 1059 } 1060 if (!ldapConfigPath.empty()) 1061 { 1062 break; 1063 } 1064 } 1065 1066 if (ldapConfigPath.empty()) 1067 { 1068 return userInfo; 1069 } 1070 1071 for (const auto& obj : objects) 1072 { 1073 for (const auto& interface : obj.second) 1074 { 1075 if ((interface.first == 1076 "xyz.openbmc_project.User.PrivilegeMapperEntry") && 1077 (obj.first.str.find(ldapConfigPath) != 1078 std::string::npos)) 1079 { 1080 1081 for (const auto& property : interface.second) 1082 { 1083 auto value = std::get<std::string>(property.second); 1084 if (property.first == "GroupName") 1085 { 1086 groupName = value; 1087 } 1088 else if (property.first == "Privilege") 1089 { 1090 privilege = value; 1091 } 1092 if (groupName == ldapGroupName) 1093 { 1094 userInfo["UserPrivilege"] = privilege; 1095 } 1096 } 1097 } 1098 } 1099 } 1100 auto priv = std::get<std::string>(userInfo["UserPrivilege"]); 1101 1102 if (priv.empty()) 1103 { 1104 log<level::ERR>("LDAP group privilege mapping does not exist"); 1105 } 1106 } 1107 catch (const std::bad_variant_access& e) 1108 { 1109 log<level::ERR>("Error while accessing variant", 1110 entry("WHAT=%s", e.what())); 1111 elog<InternalFailure>(); 1112 } 1113 userInfo.emplace("RemoteUser", true); 1114 } 1115 1116 return userInfo; 1117 } 1118 1119 void UserMgr::initUserObjects(void) 1120 { 1121 // All user management lock has to be based on /etc/shadow 1122 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{}; 1123 std::vector<std::string> userNameList; 1124 std::vector<std::string> sshGrpUsersList; 1125 UserSSHLists userSSHLists = getUserAndSshGrpList(); 1126 userNameList = std::move(userSSHLists.first); 1127 sshGrpUsersList = std::move(userSSHLists.second); 1128 1129 if (!userNameList.empty()) 1130 { 1131 std::map<std::string, std::vector<std::string>> groupLists; 1132 for (auto& grp : groupsMgr) 1133 { 1134 if (grp == grpSsh) 1135 { 1136 groupLists.emplace(grp, sshGrpUsersList); 1137 } 1138 else 1139 { 1140 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1141 groupLists.emplace(grp, grpUsersList); 1142 } 1143 } 1144 for (auto& grp : privMgr) 1145 { 1146 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 1147 groupLists.emplace(grp, grpUsersList); 1148 } 1149 1150 for (auto& user : userNameList) 1151 { 1152 std::vector<std::string> userGroups; 1153 std::string userPriv; 1154 for (const auto& grp : groupLists) 1155 { 1156 std::vector<std::string> tempGrp = grp.second; 1157 if (std::find(tempGrp.begin(), tempGrp.end(), user) != 1158 tempGrp.end()) 1159 { 1160 if (std::find(privMgr.begin(), privMgr.end(), grp.first) != 1161 privMgr.end()) 1162 { 1163 userPriv = grp.first; 1164 } 1165 else 1166 { 1167 userGroups.emplace_back(grp.first); 1168 } 1169 } 1170 } 1171 // Add user objects to the Users path. 1172 sdbusplus::message::object_path tempObjPath(usersObjPath); 1173 tempObjPath /= user; 1174 std::string objPath(tempObjPath); 1175 std::sort(userGroups.begin(), userGroups.end()); 1176 usersList.emplace(user, 1177 std::move(std::make_unique<phosphor::user::Users>( 1178 bus, objPath.c_str(), userGroups, userPriv, 1179 isUserEnabled(user), *this))); 1180 } 1181 } 1182 } 1183 1184 UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) : 1185 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path) 1186 { 1187 UserMgrIface::allPrivileges(privMgr); 1188 std::sort(groupsMgr.begin(), groupsMgr.end()); 1189 UserMgrIface::allGroups(groupsMgr); 1190 std::string valueStr; 1191 auto value = minPasswdLength; 1192 unsigned long tmp = 0; 1193 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) != 1194 success) 1195 { 1196 AccountPolicyIface::minPasswordLength(minPasswdLength); 1197 } 1198 else 1199 { 1200 try 1201 { 1202 tmp = std::stoul(valueStr, nullptr); 1203 if (tmp > std::numeric_limits<decltype(value)>::max()) 1204 { 1205 throw std::out_of_range("Out of range"); 1206 } 1207 value = static_cast<decltype(value)>(tmp); 1208 } 1209 catch (const std::exception& e) 1210 { 1211 log<level::ERR>("Exception for MinPasswordLength", 1212 entry("WHAT=%s", e.what())); 1213 throw; 1214 } 1215 AccountPolicyIface::minPasswordLength(value); 1216 } 1217 valueStr.clear(); 1218 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) != 1219 success) 1220 { 1221 AccountPolicyIface::rememberOldPasswordTimes(0); 1222 } 1223 else 1224 { 1225 value = 0; 1226 try 1227 { 1228 tmp = std::stoul(valueStr, nullptr); 1229 if (tmp > std::numeric_limits<decltype(value)>::max()) 1230 { 1231 throw std::out_of_range("Out of range"); 1232 } 1233 value = static_cast<decltype(value)>(tmp); 1234 } 1235 catch (const std::exception& e) 1236 { 1237 log<level::ERR>("Exception for RememberOldPasswordTimes", 1238 entry("WHAT=%s", e.what())); 1239 throw; 1240 } 1241 AccountPolicyIface::rememberOldPasswordTimes(value); 1242 } 1243 valueStr.clear(); 1244 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success) 1245 { 1246 AccountPolicyIface::maxLoginAttemptBeforeLockout(0); 1247 } 1248 else 1249 { 1250 uint16_t value16 = 0; 1251 try 1252 { 1253 tmp = std::stoul(valueStr, nullptr); 1254 if (tmp > std::numeric_limits<decltype(value16)>::max()) 1255 { 1256 throw std::out_of_range("Out of range"); 1257 } 1258 value16 = static_cast<decltype(value16)>(tmp); 1259 } 1260 catch (const std::exception& e) 1261 { 1262 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout", 1263 entry("WHAT=%s", e.what())); 1264 throw; 1265 } 1266 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16); 1267 } 1268 valueStr.clear(); 1269 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success) 1270 { 1271 AccountPolicyIface::accountUnlockTimeout(0); 1272 } 1273 else 1274 { 1275 uint32_t value32 = 0; 1276 try 1277 { 1278 tmp = std::stoul(valueStr, nullptr); 1279 if (tmp > std::numeric_limits<decltype(value32)>::max()) 1280 { 1281 throw std::out_of_range("Out of range"); 1282 } 1283 value32 = static_cast<decltype(value32)>(tmp); 1284 } 1285 catch (const std::exception& e) 1286 { 1287 log<level::ERR>("Exception for AccountUnlockTimeout", 1288 entry("WHAT=%s", e.what())); 1289 throw; 1290 } 1291 AccountPolicyIface::accountUnlockTimeout(value32); 1292 } 1293 initUserObjects(); 1294 1295 // emit the signal 1296 this->emit_object_added(); 1297 } 1298 1299 } // namespace user 1300 } // namespace phosphor 1301