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 <shadow.h> 18 #include <unistd.h> 19 #include <sys/types.h> 20 #include <sys/wait.h> 21 #include <fstream> 22 #include <grp.h> 23 #include <pwd.h> 24 #include <regex> 25 #include <algorithm> 26 #include <numeric> 27 #include <boost/process/child.hpp> 28 #include <xyz/openbmc_project/Common/error.hpp> 29 #include <xyz/openbmc_project/User/Common/error.hpp> 30 #include <phosphor-logging/log.hpp> 31 #include <phosphor-logging/elog.hpp> 32 #include <phosphor-logging/elog-errors.hpp> 33 #include "shadowlock.hpp" 34 #include "file.hpp" 35 #include "user_mgr.hpp" 36 #include "users.hpp" 37 #include "config.h" 38 39 namespace phosphor 40 { 41 namespace user 42 { 43 44 static constexpr const char *passwdFileName = "/etc/passwd"; 45 static constexpr size_t ipmiMaxUsers = 15; 46 static constexpr size_t ipmiMaxUserNameLen = 16; 47 static constexpr size_t systemMaxUserNameLen = 30; 48 static constexpr size_t maxSystemUsers = 30; 49 static constexpr const char *grpSsh = "ssh"; 50 51 using namespace phosphor::logging; 52 using InsufficientPermission = 53 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission; 54 using InternalFailure = 55 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 56 using InvalidArgument = 57 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; 58 using UserNameExists = 59 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists; 60 using UserNameDoesNotExist = 61 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist; 62 using UserNameGroupFail = 63 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail; 64 65 using NoResource = 66 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource; 67 68 using Argument = xyz::openbmc_project::Common::InvalidArgument; 69 70 template <typename... ArgTypes> 71 static void executeCmd(const char *path, ArgTypes &&... tArgs) 72 { 73 boost::process::child execProg(path, const_cast<char *>(tArgs)...); 74 execProg.wait(); 75 int retCode = execProg.exit_code(); 76 if (retCode) 77 { 78 log<level::ERR>("Command execution failed", entry("PATH=%s", path), 79 entry("RETURN_CODE:%d", retCode)); 80 elog<InternalFailure>(); 81 } 82 return; 83 } 84 85 static std::string getCSVFromVector(std::vector<std::string> vec) 86 { 87 switch (vec.size()) 88 { 89 case 0: 90 { 91 return ""; 92 } 93 break; 94 95 case 1: 96 { 97 return std::string{vec[0]}; 98 } 99 break; 100 101 default: 102 { 103 return std::accumulate( 104 std::next(vec.begin()), vec.end(), vec[0], 105 [](std::string a, std::string b) { return a + ',' + b; }); 106 } 107 } 108 } 109 110 static bool removeStringFromCSV(std::string &csvStr, const std::string &delStr) 111 { 112 std::string::size_type delStrPos = csvStr.find(delStr); 113 if (delStrPos != std::string::npos) 114 { 115 // need to also delete the comma char 116 if (delStrPos == 0) 117 { 118 csvStr.erase(delStrPos, delStr.size() + 1); 119 } 120 else 121 { 122 csvStr.erase(delStrPos - 1, delStr.size() + 1); 123 } 124 return true; 125 } 126 return false; 127 } 128 129 bool UserMgr::isUserExist(const std::string &userName) 130 { 131 if (userName.empty()) 132 { 133 log<level::ERR>("User name is empty"); 134 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 135 Argument::ARGUMENT_VALUE("Null")); 136 } 137 if (usersList.find(userName) == usersList.end()) 138 { 139 return false; 140 } 141 return true; 142 } 143 144 void UserMgr::throwForUserDoesNotExist(const std::string &userName) 145 { 146 if (isUserExist(userName) == false) 147 { 148 log<level::ERR>("User does not exist", 149 entry("USER_NAME=%s", userName.c_str())); 150 elog<UserNameDoesNotExist>(); 151 } 152 } 153 154 void UserMgr::throwForUserExists(const std::string &userName) 155 { 156 if (isUserExist(userName) == true) 157 { 158 log<level::ERR>("User already exists", 159 entry("USER_NAME=%s", userName.c_str())); 160 elog<UserNameExists>(); 161 } 162 } 163 164 void UserMgr::throwForUserNameConstraints( 165 const std::string &userName, const std::vector<std::string> &groupNames) 166 { 167 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != 168 groupNames.end()) 169 { 170 if (userName.length() > ipmiMaxUserNameLen) 171 { 172 log<level::ERR>("IPMI user name length limitation", 173 entry("SIZE=%d", userName.length())); 174 elog<UserNameGroupFail>( 175 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON( 176 "IPMI length")); 177 } 178 } 179 if (userName.length() > systemMaxUserNameLen) 180 { 181 log<level::ERR>("User name length limitation", 182 entry("SIZE=%d", userName.length())); 183 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 184 Argument::ARGUMENT_VALUE("Invalid length")); 185 } 186 if (!std::regex_match(userName.c_str(), 187 std::regex("[a-zA-z_][a-zA-Z_0-9]*"))) 188 { 189 log<level::ERR>("Invalid user name", 190 entry("USER_NAME=%s", userName.c_str())); 191 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"), 192 Argument::ARGUMENT_VALUE("Invalid data")); 193 } 194 } 195 196 void UserMgr::throwForMaxGrpUserCount( 197 const std::vector<std::string> &groupNames) 198 { 199 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") != 200 groupNames.end()) 201 { 202 if (getIpmiUsersCount() >= ipmiMaxUsers) 203 { 204 log<level::ERR>("IPMI user limit reached"); 205 elog<NoResource>( 206 xyz::openbmc_project::User::Common::NoResource::REASON( 207 "ipmi user count reached")); 208 } 209 } 210 else 211 { 212 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >= 213 (maxSystemUsers - ipmiMaxUsers)) 214 { 215 log<level::ERR>("Non-ipmi User limit reached"); 216 elog<NoResource>( 217 xyz::openbmc_project::User::Common::NoResource::REASON( 218 "Non-ipmi user count reached")); 219 } 220 } 221 return; 222 } 223 224 void UserMgr::throwForInvalidPrivilege(const std::string &priv) 225 { 226 if (!priv.empty() && 227 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end())) 228 { 229 log<level::ERR>("Invalid privilege"); 230 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"), 231 Argument::ARGUMENT_VALUE(priv.c_str())); 232 } 233 } 234 235 void UserMgr::throwForInvalidGroups(const std::vector<std::string> &groupNames) 236 { 237 for (auto &group : groupNames) 238 { 239 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) == 240 groupsMgr.end()) 241 { 242 log<level::ERR>("Invalid Group Name listed"); 243 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"), 244 Argument::ARGUMENT_VALUE(group.c_str())); 245 } 246 } 247 } 248 249 void UserMgr::createUser(std::string userName, 250 std::vector<std::string> groupNames, std::string priv, 251 bool enabled) 252 { 253 throwForInvalidPrivilege(priv); 254 throwForInvalidGroups(groupNames); 255 // All user management lock has to be based on /etc/shadow 256 phosphor::user::shadow::Lock lock(); 257 throwForUserExists(userName); 258 throwForUserNameConstraints(userName, groupNames); 259 throwForMaxGrpUserCount(groupNames); 260 261 std::string groups = getCSVFromVector(groupNames); 262 bool sshRequested = removeStringFromCSV(groups, grpSsh); 263 264 // treat privilege as a group - This is to avoid using different file to 265 // store the same. 266 if (groups.size() != 0) 267 { 268 groups += ","; 269 } 270 groups += priv; 271 272 try 273 { 274 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(), 275 "-M", "-N", "-s", 276 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e", 277 (enabled ? "" : "1970-01-02")); 278 } 279 catch (const InternalFailure &e) 280 { 281 log<level::ERR>("Unable to create new user"); 282 elog<InternalFailure>(); 283 } 284 285 // Add the users object before sending out the signal 286 std::string userObj = std::string(usersObjPath) + "/" + userName; 287 std::sort(groupNames.begin(), groupNames.end()); 288 usersList.emplace( 289 userName, std::move(std::make_unique<phosphor::user::Users>( 290 bus, userObj.c_str(), groupNames, priv, enabled, *this))); 291 292 log<level::INFO>("User created successfully", 293 entry("USER_NAME=%s", userName.c_str())); 294 return; 295 } 296 297 void UserMgr::deleteUser(std::string userName) 298 { 299 // All user management lock has to be based on /etc/shadow 300 phosphor::user::shadow::Lock lock(); 301 throwForUserDoesNotExist(userName); 302 try 303 { 304 executeCmd("/usr/sbin/userdel", userName.c_str()); 305 } 306 catch (const InternalFailure &e) 307 { 308 log<level::ERR>("User delete failed", 309 entry("USER_NAME=%s", userName.c_str())); 310 elog<InternalFailure>(); 311 } 312 313 usersList.erase(userName); 314 315 log<level::INFO>("User deleted successfully", 316 entry("USER_NAME=%s", userName.c_str())); 317 return; 318 } 319 320 void UserMgr::renameUser(std::string userName, std::string newUserName) 321 { 322 // All user management lock has to be based on /etc/shadow 323 phosphor::user::shadow::Lock lock(); 324 throwForUserDoesNotExist(userName); 325 throwForUserExists(newUserName); 326 throwForUserNameConstraints(newUserName, 327 usersList[userName].get()->userGroups()); 328 try 329 { 330 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(), 331 userName.c_str()); 332 } 333 catch (const InternalFailure &e) 334 { 335 log<level::ERR>("User rename failed", 336 entry("USER_NAME=%s", userName.c_str())); 337 elog<InternalFailure>(); 338 } 339 const auto &user = usersList[userName]; 340 std::string priv = user.get()->userPrivilege(); 341 std::vector<std::string> groupNames = user.get()->userGroups(); 342 bool enabled = user.get()->userEnabled(); 343 std::string newUserObj = std::string(usersObjPath) + "/" + newUserName; 344 // Special group 'ipmi' needs a way to identify user renamed, in order to 345 // update encrypted password. It can't rely only on InterfacesRemoved & 346 // InterfacesAdded. So first send out userRenamed signal. 347 this->userRenamed(userName, newUserName); 348 usersList.erase(userName); 349 usersList.emplace( 350 newUserName, 351 std::move(std::make_unique<phosphor::user::Users>( 352 bus, newUserObj.c_str(), groupNames, priv, enabled, *this))); 353 return; 354 } 355 356 void UserMgr::updateGroupsAndPriv(const std::string &userName, 357 const std::vector<std::string> &groupNames, 358 const std::string &priv) 359 { 360 throwForInvalidPrivilege(priv); 361 throwForInvalidGroups(groupNames); 362 // All user management lock has to be based on /etc/shadow 363 phosphor::user::shadow::Lock lock(); 364 throwForUserDoesNotExist(userName); 365 const std::vector<std::string> &oldGroupNames = 366 usersList[userName].get()->userGroups(); 367 std::vector<std::string> groupDiff; 368 // Note: already dealing with sorted group lists. 369 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(), 370 groupNames.begin(), groupNames.end(), 371 std::back_inserter(groupDiff)); 372 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") != 373 groupDiff.end()) 374 { 375 throwForUserNameConstraints(userName, groupNames); 376 throwForMaxGrpUserCount(groupNames); 377 } 378 379 std::string groups = getCSVFromVector(groupNames); 380 bool sshRequested = removeStringFromCSV(groups, grpSsh); 381 382 // treat privilege as a group - This is to avoid using different file to 383 // store the same. 384 if (groups.size() != 0) 385 { 386 groups += ","; 387 } 388 groups += priv; 389 try 390 { 391 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(), 392 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin")); 393 } 394 catch (const InternalFailure &e) 395 { 396 log<level::ERR>("Unable to modify user privilege / groups"); 397 elog<InternalFailure>(); 398 } 399 400 log<level::INFO>("User groups / privilege updated successfully", 401 entry("USER_NAME=%s", userName.c_str())); 402 return; 403 } 404 405 void UserMgr::userEnable(const std::string &userName, bool enabled) 406 { 407 // All user management lock has to be based on /etc/shadow 408 phosphor::user::shadow::Lock lock(); 409 throwForUserDoesNotExist(userName); 410 try 411 { 412 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e", 413 (enabled ? "" : "1970-01-02")); 414 } 415 catch (const InternalFailure &e) 416 { 417 log<level::ERR>("Unable to modify user enabled state"); 418 elog<InternalFailure>(); 419 } 420 421 log<level::INFO>("User enabled/disabled state updated successfully", 422 entry("USER_NAME=%s", userName.c_str()), 423 entry("ENABLED=%d", enabled)); 424 return; 425 } 426 427 UserSSHLists UserMgr::getUserAndSshGrpList() 428 { 429 // All user management lock has to be based on /etc/shadow 430 phosphor::user::shadow::Lock lock(); 431 432 std::vector<std::string> userList; 433 std::vector<std::string> sshUsersList; 434 struct passwd pw, *pwp = nullptr; 435 std::array<char, 1024> buffer{}; 436 437 phosphor::user::File passwd(passwdFileName, "r"); 438 if ((passwd)() == NULL) 439 { 440 log<level::ERR>("Error opening the passwd file"); 441 elog<InternalFailure>(); 442 } 443 444 while (true) 445 { 446 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(), 447 &pwp); 448 if ((r != 0) || (pwp == NULL)) 449 { 450 // Any error, break the loop. 451 break; 452 } 453 // All users whose UID >= 1000 and < 65534 454 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)) 455 { 456 std::string userName(pwp->pw_name); 457 userList.emplace_back(userName); 458 459 // ssh doesn't have separate group. Check login shell entry to 460 // get all users list which are member of ssh group. 461 std::string loginShell(pwp->pw_shell); 462 if (loginShell == "/bin/sh") 463 { 464 sshUsersList.emplace_back(userName); 465 } 466 } 467 } 468 endpwent(); 469 return std::make_pair(std::move(userList), std::move(sshUsersList)); 470 } 471 472 size_t UserMgr::getIpmiUsersCount() 473 { 474 std::vector<std::string> userList = getUsersInGroup("ipmi"); 475 return userList.size(); 476 } 477 478 bool UserMgr::isUserEnabled(const std::string &userName) 479 { 480 // All user management lock has to be based on /etc/shadow 481 phosphor::user::shadow::Lock lock(); 482 std::array<char, 4096> buffer{}; 483 struct spwd spwd; 484 struct spwd *resultPtr = nullptr; 485 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(), 486 buffer.max_size(), &resultPtr); 487 if (!status && (&spwd == resultPtr)) 488 { 489 if (resultPtr->sp_expire >= 0) 490 { 491 return false; // user locked out 492 } 493 return true; 494 } 495 return false; // assume user is disabled for any error. 496 } 497 498 std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName) 499 { 500 std::vector<std::string> usersInGroup; 501 // Should be more than enough to get the pwd structure. 502 std::array<char, 4096> buffer{}; 503 struct group grp; 504 struct group *resultPtr = nullptr; 505 506 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(), 507 buffer.max_size(), &resultPtr); 508 509 if (!status && (&grp == resultPtr)) 510 { 511 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem)) 512 { 513 usersInGroup.emplace_back(*(grp.gr_mem)); 514 } 515 } 516 else 517 { 518 log<level::ERR>("Group not found", 519 entry("GROUP=%s", groupName.c_str())); 520 // Don't throw error, just return empty userList - fallback 521 } 522 return usersInGroup; 523 } 524 525 void UserMgr::initUserObjects(void) 526 { 527 // All user management lock has to be based on /etc/shadow 528 phosphor::user::shadow::Lock lock(); 529 std::vector<std::string> userNameList; 530 std::vector<std::string> sshGrpUsersList; 531 UserSSHLists userSSHLists = getUserAndSshGrpList(); 532 userNameList = std::move(userSSHLists.first); 533 sshGrpUsersList = std::move(userSSHLists.second); 534 535 if (!userNameList.empty()) 536 { 537 std::map<std::string, std::vector<std::string>> groupLists; 538 for (auto &grp : groupsMgr) 539 { 540 if (grp == grpSsh) 541 { 542 groupLists.emplace(grp, sshGrpUsersList); 543 } 544 else 545 { 546 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 547 groupLists.emplace(grp, grpUsersList); 548 } 549 } 550 for (auto &grp : privMgr) 551 { 552 std::vector<std::string> grpUsersList = getUsersInGroup(grp); 553 groupLists.emplace(grp, grpUsersList); 554 } 555 556 for (auto &user : userNameList) 557 { 558 std::vector<std::string> userGroups; 559 std::string userPriv; 560 for (const auto &grp : groupLists) 561 { 562 std::vector<std::string> tempGrp = grp.second; 563 if (std::find(tempGrp.begin(), tempGrp.end(), user) != 564 tempGrp.end()) 565 { 566 if (std::find(privMgr.begin(), privMgr.end(), grp.first) != 567 privMgr.end()) 568 { 569 userPriv = grp.first; 570 } 571 else 572 { 573 userGroups.emplace_back(grp.first); 574 } 575 } 576 } 577 // Add user objects to the Users path. 578 auto objPath = std::string(usersObjPath) + "/" + user; 579 std::sort(userGroups.begin(), userGroups.end()); 580 usersList.emplace(user, 581 std::move(std::make_unique<phosphor::user::Users>( 582 bus, objPath.c_str(), userGroups, userPriv, 583 isUserEnabled(user), *this))); 584 } 585 } 586 } 587 588 UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) : 589 UserMgrIface(bus, path), bus(bus), path(path) 590 { 591 UserMgrIface::allPrivileges(privMgr); 592 std::sort(groupsMgr.begin(), groupsMgr.end()); 593 UserMgrIface::allGroups(groupsMgr); 594 initUserObjects(); 595 } 596 597 } // namespace user 598 } // namespace phosphor 599