blob: ee72b05a9f3f5d91be2be42248f66a222688cc95 [file] [log] [blame]
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301/*
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
39namespace phosphor
40{
41namespace user
42{
43
44static constexpr const char *passwdFileName = "/etc/passwd";
45static constexpr size_t ipmiMaxUsers = 15;
46static constexpr size_t ipmiMaxUserNameLen = 16;
47static constexpr size_t systemMaxUserNameLen = 30;
48static constexpr size_t maxSystemUsers = 30;
49static constexpr const char *grpSsh = "ssh";
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +053050static constexpr uint8_t minPasswdLength = 8;
51static constexpr int success = 0;
52static constexpr int failure = -1;
53
54// pam modules related
55static constexpr const char *pamTally2 = "pam_tally2.so";
56static constexpr const char *pamCrackLib = "pam_cracklib.so";
57static constexpr const char *pamPWHistory = "pam_pwhistory.so";
58static constexpr const char *minPasswdLenProp = "minlen";
59static constexpr const char *remOldPasswdCount = "remember";
60static constexpr const char *maxFailedAttempt = "deny";
61static constexpr const char *unlockTimeout = "unlock_time";
62static constexpr const char *pamPasswdConfigFile = "/etc/pam.d/common-password";
63static constexpr const char *pamAuthConfigFile = "/etc/pam.d/common-auth";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053064
65using namespace phosphor::logging;
66using InsufficientPermission =
67 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
68using InternalFailure =
69 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
70using InvalidArgument =
71 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
72using UserNameExists =
73 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
74using UserNameDoesNotExist =
75 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
76using UserNameGroupFail =
77 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
78
79using NoResource =
80 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
81
82using Argument = xyz::openbmc_project::Common::InvalidArgument;
83
84template <typename... ArgTypes>
85static void executeCmd(const char *path, ArgTypes &&... tArgs)
86{
87 boost::process::child execProg(path, const_cast<char *>(tArgs)...);
88 execProg.wait();
89 int retCode = execProg.exit_code();
90 if (retCode)
91 {
92 log<level::ERR>("Command execution failed", entry("PATH=%s", path),
93 entry("RETURN_CODE:%d", retCode));
94 elog<InternalFailure>();
95 }
96 return;
97}
98
99static std::string getCSVFromVector(std::vector<std::string> vec)
100{
101 switch (vec.size())
102 {
103 case 0:
104 {
105 return "";
106 }
107 break;
108
109 case 1:
110 {
111 return std::string{vec[0]};
112 }
113 break;
114
115 default:
116 {
117 return std::accumulate(
118 std::next(vec.begin()), vec.end(), vec[0],
119 [](std::string a, std::string b) { return a + ',' + b; });
120 }
121 }
122}
123
124static bool removeStringFromCSV(std::string &csvStr, const std::string &delStr)
125{
126 std::string::size_type delStrPos = csvStr.find(delStr);
127 if (delStrPos != std::string::npos)
128 {
129 // need to also delete the comma char
130 if (delStrPos == 0)
131 {
132 csvStr.erase(delStrPos, delStr.size() + 1);
133 }
134 else
135 {
136 csvStr.erase(delStrPos - 1, delStr.size() + 1);
137 }
138 return true;
139 }
140 return false;
141}
142
143bool UserMgr::isUserExist(const std::string &userName)
144{
145 if (userName.empty())
146 {
147 log<level::ERR>("User name is empty");
148 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
149 Argument::ARGUMENT_VALUE("Null"));
150 }
151 if (usersList.find(userName) == usersList.end())
152 {
153 return false;
154 }
155 return true;
156}
157
158void UserMgr::throwForUserDoesNotExist(const std::string &userName)
159{
160 if (isUserExist(userName) == false)
161 {
162 log<level::ERR>("User does not exist",
163 entry("USER_NAME=%s", userName.c_str()));
164 elog<UserNameDoesNotExist>();
165 }
166}
167
168void UserMgr::throwForUserExists(const std::string &userName)
169{
170 if (isUserExist(userName) == true)
171 {
172 log<level::ERR>("User already exists",
173 entry("USER_NAME=%s", userName.c_str()));
174 elog<UserNameExists>();
175 }
176}
177
178void UserMgr::throwForUserNameConstraints(
179 const std::string &userName, const std::vector<std::string> &groupNames)
180{
181 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
182 groupNames.end())
183 {
184 if (userName.length() > ipmiMaxUserNameLen)
185 {
186 log<level::ERR>("IPMI user name length limitation",
187 entry("SIZE=%d", userName.length()));
188 elog<UserNameGroupFail>(
189 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
190 "IPMI length"));
191 }
192 }
193 if (userName.length() > systemMaxUserNameLen)
194 {
195 log<level::ERR>("User name length limitation",
196 entry("SIZE=%d", userName.length()));
197 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
198 Argument::ARGUMENT_VALUE("Invalid length"));
199 }
200 if (!std::regex_match(userName.c_str(),
201 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
202 {
203 log<level::ERR>("Invalid user name",
204 entry("USER_NAME=%s", userName.c_str()));
205 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
206 Argument::ARGUMENT_VALUE("Invalid data"));
207 }
208}
209
210void UserMgr::throwForMaxGrpUserCount(
211 const std::vector<std::string> &groupNames)
212{
213 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
214 groupNames.end())
215 {
216 if (getIpmiUsersCount() >= ipmiMaxUsers)
217 {
218 log<level::ERR>("IPMI user limit reached");
219 elog<NoResource>(
220 xyz::openbmc_project::User::Common::NoResource::REASON(
221 "ipmi user count reached"));
222 }
223 }
224 else
225 {
226 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
227 (maxSystemUsers - ipmiMaxUsers))
228 {
229 log<level::ERR>("Non-ipmi User limit reached");
230 elog<NoResource>(
231 xyz::openbmc_project::User::Common::NoResource::REASON(
232 "Non-ipmi user count reached"));
233 }
234 }
235 return;
236}
237
238void UserMgr::throwForInvalidPrivilege(const std::string &priv)
239{
240 if (!priv.empty() &&
241 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
242 {
243 log<level::ERR>("Invalid privilege");
244 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
245 Argument::ARGUMENT_VALUE(priv.c_str()));
246 }
247}
248
249void UserMgr::throwForInvalidGroups(const std::vector<std::string> &groupNames)
250{
251 for (auto &group : groupNames)
252 {
253 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
254 groupsMgr.end())
255 {
256 log<level::ERR>("Invalid Group Name listed");
257 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
258 Argument::ARGUMENT_VALUE(group.c_str()));
259 }
260 }
261}
262
263void UserMgr::createUser(std::string userName,
264 std::vector<std::string> groupNames, std::string priv,
265 bool enabled)
266{
267 throwForInvalidPrivilege(priv);
268 throwForInvalidGroups(groupNames);
269 // All user management lock has to be based on /etc/shadow
270 phosphor::user::shadow::Lock lock();
271 throwForUserExists(userName);
272 throwForUserNameConstraints(userName, groupNames);
273 throwForMaxGrpUserCount(groupNames);
274
275 std::string groups = getCSVFromVector(groupNames);
276 bool sshRequested = removeStringFromCSV(groups, grpSsh);
277
278 // treat privilege as a group - This is to avoid using different file to
279 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530280 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530281 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530282 if (groups.size() != 0)
283 {
284 groups += ",";
285 }
286 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530287 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530288 try
289 {
290 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530291 "-m", "-N", "-s",
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530292 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
293 (enabled ? "" : "1970-01-02"));
294 }
295 catch (const InternalFailure &e)
296 {
297 log<level::ERR>("Unable to create new user");
298 elog<InternalFailure>();
299 }
300
301 // Add the users object before sending out the signal
302 std::string userObj = std::string(usersObjPath) + "/" + userName;
303 std::sort(groupNames.begin(), groupNames.end());
304 usersList.emplace(
305 userName, std::move(std::make_unique<phosphor::user::Users>(
306 bus, userObj.c_str(), groupNames, priv, enabled, *this)));
307
308 log<level::INFO>("User created successfully",
309 entry("USER_NAME=%s", userName.c_str()));
310 return;
311}
312
313void UserMgr::deleteUser(std::string userName)
314{
315 // All user management lock has to be based on /etc/shadow
316 phosphor::user::shadow::Lock lock();
317 throwForUserDoesNotExist(userName);
318 try
319 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530320 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530321 }
322 catch (const InternalFailure &e)
323 {
324 log<level::ERR>("User delete failed",
325 entry("USER_NAME=%s", userName.c_str()));
326 elog<InternalFailure>();
327 }
328
329 usersList.erase(userName);
330
331 log<level::INFO>("User deleted successfully",
332 entry("USER_NAME=%s", userName.c_str()));
333 return;
334}
335
336void UserMgr::renameUser(std::string userName, std::string newUserName)
337{
338 // All user management lock has to be based on /etc/shadow
339 phosphor::user::shadow::Lock lock();
340 throwForUserDoesNotExist(userName);
341 throwForUserExists(newUserName);
342 throwForUserNameConstraints(newUserName,
343 usersList[userName].get()->userGroups());
344 try
345 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530346 std::string newHomeDir = "/home/" + newUserName;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530347 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530348 userName.c_str(), "-d", newHomeDir.c_str(), "-m");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530349 }
350 catch (const InternalFailure &e)
351 {
352 log<level::ERR>("User rename failed",
353 entry("USER_NAME=%s", userName.c_str()));
354 elog<InternalFailure>();
355 }
356 const auto &user = usersList[userName];
357 std::string priv = user.get()->userPrivilege();
358 std::vector<std::string> groupNames = user.get()->userGroups();
359 bool enabled = user.get()->userEnabled();
360 std::string newUserObj = std::string(usersObjPath) + "/" + newUserName;
361 // Special group 'ipmi' needs a way to identify user renamed, in order to
362 // update encrypted password. It can't rely only on InterfacesRemoved &
363 // InterfacesAdded. So first send out userRenamed signal.
364 this->userRenamed(userName, newUserName);
365 usersList.erase(userName);
366 usersList.emplace(
367 newUserName,
368 std::move(std::make_unique<phosphor::user::Users>(
369 bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
370 return;
371}
372
373void UserMgr::updateGroupsAndPriv(const std::string &userName,
374 const std::vector<std::string> &groupNames,
375 const std::string &priv)
376{
377 throwForInvalidPrivilege(priv);
378 throwForInvalidGroups(groupNames);
379 // All user management lock has to be based on /etc/shadow
380 phosphor::user::shadow::Lock lock();
381 throwForUserDoesNotExist(userName);
382 const std::vector<std::string> &oldGroupNames =
383 usersList[userName].get()->userGroups();
384 std::vector<std::string> groupDiff;
385 // Note: already dealing with sorted group lists.
386 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
387 groupNames.begin(), groupNames.end(),
388 std::back_inserter(groupDiff));
389 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
390 groupDiff.end())
391 {
392 throwForUserNameConstraints(userName, groupNames);
393 throwForMaxGrpUserCount(groupNames);
394 }
395
396 std::string groups = getCSVFromVector(groupNames);
397 bool sshRequested = removeStringFromCSV(groups, grpSsh);
398
399 // treat privilege as a group - This is to avoid using different file to
400 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530401 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530402 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530403 if (groups.size() != 0)
404 {
405 groups += ",";
406 }
407 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530408 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530409 try
410 {
411 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
412 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
413 }
414 catch (const InternalFailure &e)
415 {
416 log<level::ERR>("Unable to modify user privilege / groups");
417 elog<InternalFailure>();
418 }
419
420 log<level::INFO>("User groups / privilege updated successfully",
421 entry("USER_NAME=%s", userName.c_str()));
422 return;
423}
424
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530425uint8_t UserMgr::minPasswordLength(uint8_t value)
426{
427 if (value == AccountPolicyIface::minPasswordLength())
428 {
429 return value;
430 }
431 if (value < minPasswdLength)
432 {
433 return value;
434 }
435 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
436 std::to_string(value)) != success)
437 {
438 log<level::ERR>("Unable to set minPasswordLength");
439 elog<InternalFailure>();
440 }
441 return AccountPolicyIface::minPasswordLength(value);
442}
443
444uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
445{
446 if (value == AccountPolicyIface::rememberOldPasswordTimes())
447 {
448 return value;
449 }
450 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
451 std::to_string(value)) != success)
452 {
453 log<level::ERR>("Unable to set rememberOldPasswordTimes");
454 elog<InternalFailure>();
455 }
456 return AccountPolicyIface::rememberOldPasswordTimes(value);
457}
458
459uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
460{
461 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
462 {
463 return value;
464 }
465 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
466 std::to_string(value)) != success)
467 {
468 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
469 elog<InternalFailure>();
470 }
471 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
472}
473
474uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
475{
476 if (value == AccountPolicyIface::accountUnlockTimeout())
477 {
478 return value;
479 }
480 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
481 success)
482 {
483 log<level::ERR>("Unable to set accountUnlockTimeout");
484 elog<InternalFailure>();
485 }
486 return AccountPolicyIface::accountUnlockTimeout(value);
487}
488
489int UserMgr::getPamModuleArgValue(const std::string &moduleName,
490 const std::string &argName,
491 std::string &argValue)
492{
493 std::string fileName;
494 if (moduleName == pamTally2)
495 {
496 fileName = pamAuthConfigFile;
497 }
498 else
499 {
500 fileName = pamPasswdConfigFile;
501 }
502 std::ifstream fileToRead(fileName, std::ios::in);
503 if (!fileToRead.is_open())
504 {
505 log<level::ERR>("Failed to open pam configuration file",
506 entry("FILE_NAME=%s", fileName.c_str()));
507 return failure;
508 }
509 std::string line;
510 auto argSearch = argName + "=";
511 size_t startPos = 0;
512 size_t endPos = 0;
513 while (getline(fileToRead, line))
514 {
515 // skip comments section starting with #
516 if ((startPos = line.find('#')) != std::string::npos)
517 {
518 if (startPos == 0)
519 {
520 continue;
521 }
522 // skip comments after meaningful section and process those
523 line = line.substr(0, startPos);
524 }
525 if (line.find(moduleName) != std::string::npos)
526 {
527 if ((startPos = line.find(argSearch)) != std::string::npos)
528 {
529 if ((endPos = line.find(' ', startPos)) == std::string::npos)
530 {
531 endPos = line.size();
532 }
533 startPos += argSearch.size();
534 argValue = line.substr(startPos, endPos - startPos);
535 return success;
536 }
537 }
538 }
539 return failure;
540}
541
542int UserMgr::setPamModuleArgValue(const std::string &moduleName,
543 const std::string &argName,
544 const std::string &argValue)
545{
546 std::string fileName;
547 if (moduleName == pamTally2)
548 {
549 fileName = pamAuthConfigFile;
550 }
551 else
552 {
553 fileName = pamPasswdConfigFile;
554 }
555 std::string tmpFileName = fileName + "_tmp";
556 std::ifstream fileToRead(fileName, std::ios::in);
557 std::ofstream fileToWrite(tmpFileName, std::ios::out);
558 if (!fileToRead.is_open() || !fileToWrite.is_open())
559 {
560 log<level::ERR>("Failed to open pam configuration /tmp file",
561 entry("FILE_NAME=%s", fileName.c_str()));
562 return failure;
563 }
564 std::string line;
565 auto argSearch = argName + "=";
566 size_t startPos = 0;
567 size_t endPos = 0;
568 bool found = false;
569 while (getline(fileToRead, line))
570 {
571 // skip comments section starting with #
572 if ((startPos = line.find('#')) != std::string::npos)
573 {
574 if (startPos == 0)
575 {
576 fileToWrite << line << std::endl;
577 continue;
578 }
579 // skip comments after meaningful section and process those
580 line = line.substr(0, startPos);
581 }
582 if (line.find(moduleName) != std::string::npos)
583 {
584 if ((startPos = line.find(argSearch)) != std::string::npos)
585 {
586 if ((endPos = line.find(' ', startPos)) == std::string::npos)
587 {
588 endPos = line.size();
589 }
590 startPos += argSearch.size();
591 fileToWrite << line.substr(0, startPos) << argValue
592 << line.substr(endPos, line.size() - endPos)
593 << std::endl;
594 found = true;
595 continue;
596 }
597 }
598 fileToWrite << line << std::endl;
599 }
600 fileToWrite.close();
601 fileToRead.close();
602 if (found)
603 {
604 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
605 {
606 return success;
607 }
608 }
609 return failure;
610}
611
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530612void UserMgr::userEnable(const std::string &userName, bool enabled)
613{
614 // All user management lock has to be based on /etc/shadow
615 phosphor::user::shadow::Lock lock();
616 throwForUserDoesNotExist(userName);
617 try
618 {
619 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
620 (enabled ? "" : "1970-01-02"));
621 }
622 catch (const InternalFailure &e)
623 {
624 log<level::ERR>("Unable to modify user enabled state");
625 elog<InternalFailure>();
626 }
627
628 log<level::INFO>("User enabled/disabled state updated successfully",
629 entry("USER_NAME=%s", userName.c_str()),
630 entry("ENABLED=%d", enabled));
631 return;
632}
633
634UserSSHLists UserMgr::getUserAndSshGrpList()
635{
636 // All user management lock has to be based on /etc/shadow
637 phosphor::user::shadow::Lock lock();
638
639 std::vector<std::string> userList;
640 std::vector<std::string> sshUsersList;
641 struct passwd pw, *pwp = nullptr;
642 std::array<char, 1024> buffer{};
643
644 phosphor::user::File passwd(passwdFileName, "r");
645 if ((passwd)() == NULL)
646 {
647 log<level::ERR>("Error opening the passwd file");
648 elog<InternalFailure>();
649 }
650
651 while (true)
652 {
653 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
654 &pwp);
655 if ((r != 0) || (pwp == NULL))
656 {
657 // Any error, break the loop.
658 break;
659 }
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530660 // Add all users whose UID >= 1000 and < 65534
661 // and special UID 0.
662 if ((pwp->pw_uid == 0) ||
663 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530664 {
665 std::string userName(pwp->pw_name);
666 userList.emplace_back(userName);
667
668 // ssh doesn't have separate group. Check login shell entry to
669 // get all users list which are member of ssh group.
670 std::string loginShell(pwp->pw_shell);
671 if (loginShell == "/bin/sh")
672 {
673 sshUsersList.emplace_back(userName);
674 }
675 }
676 }
677 endpwent();
678 return std::make_pair(std::move(userList), std::move(sshUsersList));
679}
680
681size_t UserMgr::getIpmiUsersCount()
682{
683 std::vector<std::string> userList = getUsersInGroup("ipmi");
684 return userList.size();
685}
686
687bool UserMgr::isUserEnabled(const std::string &userName)
688{
689 // All user management lock has to be based on /etc/shadow
690 phosphor::user::shadow::Lock lock();
691 std::array<char, 4096> buffer{};
692 struct spwd spwd;
693 struct spwd *resultPtr = nullptr;
694 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
695 buffer.max_size(), &resultPtr);
696 if (!status && (&spwd == resultPtr))
697 {
698 if (resultPtr->sp_expire >= 0)
699 {
700 return false; // user locked out
701 }
702 return true;
703 }
704 return false; // assume user is disabled for any error.
705}
706
707std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName)
708{
709 std::vector<std::string> usersInGroup;
710 // Should be more than enough to get the pwd structure.
711 std::array<char, 4096> buffer{};
712 struct group grp;
713 struct group *resultPtr = nullptr;
714
715 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
716 buffer.max_size(), &resultPtr);
717
718 if (!status && (&grp == resultPtr))
719 {
720 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
721 {
722 usersInGroup.emplace_back(*(grp.gr_mem));
723 }
724 }
725 else
726 {
727 log<level::ERR>("Group not found",
728 entry("GROUP=%s", groupName.c_str()));
729 // Don't throw error, just return empty userList - fallback
730 }
731 return usersInGroup;
732}
733
734void UserMgr::initUserObjects(void)
735{
736 // All user management lock has to be based on /etc/shadow
737 phosphor::user::shadow::Lock lock();
738 std::vector<std::string> userNameList;
739 std::vector<std::string> sshGrpUsersList;
740 UserSSHLists userSSHLists = getUserAndSshGrpList();
741 userNameList = std::move(userSSHLists.first);
742 sshGrpUsersList = std::move(userSSHLists.second);
743
744 if (!userNameList.empty())
745 {
746 std::map<std::string, std::vector<std::string>> groupLists;
747 for (auto &grp : groupsMgr)
748 {
749 if (grp == grpSsh)
750 {
751 groupLists.emplace(grp, sshGrpUsersList);
752 }
753 else
754 {
755 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
756 groupLists.emplace(grp, grpUsersList);
757 }
758 }
759 for (auto &grp : privMgr)
760 {
761 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
762 groupLists.emplace(grp, grpUsersList);
763 }
764
765 for (auto &user : userNameList)
766 {
767 std::vector<std::string> userGroups;
768 std::string userPriv;
769 for (const auto &grp : groupLists)
770 {
771 std::vector<std::string> tempGrp = grp.second;
772 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
773 tempGrp.end())
774 {
775 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
776 privMgr.end())
777 {
778 userPriv = grp.first;
779 }
780 else
781 {
782 userGroups.emplace_back(grp.first);
783 }
784 }
785 }
786 // Add user objects to the Users path.
787 auto objPath = std::string(usersObjPath) + "/" + user;
788 std::sort(userGroups.begin(), userGroups.end());
789 usersList.emplace(user,
790 std::move(std::make_unique<phosphor::user::Users>(
791 bus, objPath.c_str(), userGroups, userPriv,
792 isUserEnabled(user), *this)));
793 }
794 }
795}
796
797UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) :
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530798 UserMgrIface(bus, path), AccountPolicyIface(bus, path), bus(bus), path(path)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530799{
800 UserMgrIface::allPrivileges(privMgr);
801 std::sort(groupsMgr.begin(), groupsMgr.end());
802 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530803 std::string valueStr;
804 auto value = minPasswdLength;
805 unsigned long tmp = 0;
806 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
807 success)
808 {
809 AccountPolicyIface::minPasswordLength(minPasswdLength);
810 }
811 else
812 {
813 try
814 {
815 tmp = std::stoul(valueStr, nullptr);
816 if (tmp > std::numeric_limits<decltype(value)>::max())
817 {
818 throw std::out_of_range("Out of range");
819 }
820 value = static_cast<decltype(value)>(tmp);
821 }
822 catch (const std::exception &e)
823 {
824 log<level::ERR>("Exception for MinPasswordLength",
825 entry("WHAT=%s", e.what()));
826 throw e;
827 }
828 AccountPolicyIface::minPasswordLength(value);
829 }
830 valueStr.clear();
831 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
832 success)
833 {
834 AccountPolicyIface::rememberOldPasswordTimes(0);
835 }
836 else
837 {
838 value = 0;
839 try
840 {
841 tmp = std::stoul(valueStr, nullptr);
842 if (tmp > std::numeric_limits<decltype(value)>::max())
843 {
844 throw std::out_of_range("Out of range");
845 }
846 value = static_cast<decltype(value)>(tmp);
847 }
848 catch (const std::exception &e)
849 {
850 log<level::ERR>("Exception for RememberOldPasswordTimes",
851 entry("WHAT=%s", e.what()));
852 throw e;
853 }
854 AccountPolicyIface::rememberOldPasswordTimes(value);
855 }
856 valueStr.clear();
857 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
858 {
859 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
860 }
861 else
862 {
863 uint16_t value16 = 0;
864 try
865 {
866 tmp = std::stoul(valueStr, nullptr);
867 if (tmp > std::numeric_limits<decltype(value16)>::max())
868 {
869 throw std::out_of_range("Out of range");
870 }
871 value16 = static_cast<decltype(value16)>(tmp);
872 }
873 catch (const std::exception &e)
874 {
875 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
876 entry("WHAT=%s", e.what()));
877 throw e;
878 }
879 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
880 }
881 valueStr.clear();
882 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
883 {
884 AccountPolicyIface::accountUnlockTimeout(0);
885 }
886 else
887 {
888 uint32_t value32 = 0;
889 try
890 {
891 tmp = std::stoul(valueStr, nullptr);
892 if (tmp > std::numeric_limits<decltype(value32)>::max())
893 {
894 throw std::out_of_range("Out of range");
895 }
896 value32 = static_cast<decltype(value32)>(tmp);
897 }
898 catch (const std::exception &e)
899 {
900 log<level::ERR>("Exception for AccountUnlockTimeout",
901 entry("WHAT=%s", e.what()));
902 throw e;
903 }
904 AccountPolicyIface::accountUnlockTimeout(value32);
905 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530906 initUserObjects();
907}
908
909} // namespace user
910} // namespace phosphor