blob: 47edf7d282668fef704a02cec521ce265f2766a2 [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>
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +053028#include <boost/process/io.hpp>
29#include <boost/algorithm/string/split.hpp>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053030#include <xyz/openbmc_project/Common/error.hpp>
31#include <xyz/openbmc_project/User/Common/error.hpp>
32#include <phosphor-logging/log.hpp>
33#include <phosphor-logging/elog.hpp>
34#include <phosphor-logging/elog-errors.hpp>
35#include "shadowlock.hpp"
36#include "file.hpp"
37#include "user_mgr.hpp"
38#include "users.hpp"
39#include "config.h"
40
41namespace phosphor
42{
43namespace user
44{
45
46static constexpr const char *passwdFileName = "/etc/passwd";
47static constexpr size_t ipmiMaxUsers = 15;
48static constexpr size_t ipmiMaxUserNameLen = 16;
49static constexpr size_t systemMaxUserNameLen = 30;
50static constexpr size_t maxSystemUsers = 30;
51static constexpr const char *grpSsh = "ssh";
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +053052static constexpr uint8_t minPasswdLength = 8;
53static constexpr int success = 0;
54static constexpr int failure = -1;
55
56// pam modules related
57static constexpr const char *pamTally2 = "pam_tally2.so";
58static constexpr const char *pamCrackLib = "pam_cracklib.so";
59static constexpr const char *pamPWHistory = "pam_pwhistory.so";
60static constexpr const char *minPasswdLenProp = "minlen";
61static constexpr const char *remOldPasswdCount = "remember";
62static constexpr const char *maxFailedAttempt = "deny";
63static constexpr const char *unlockTimeout = "unlock_time";
64static constexpr const char *pamPasswdConfigFile = "/etc/pam.d/common-password";
65static constexpr const char *pamAuthConfigFile = "/etc/pam.d/common-auth";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053066
Ratan Guptaaeaf9412019-02-11 04:41:52 -060067// Object Manager related
68static constexpr const char *ldapMgrObjBasePath =
69 "/xyz/openbmc_project/user/ldap";
70
71// Object Mapper related
72static constexpr const char *objMapperService =
73 "xyz.openbmc_project.ObjectMapper";
74static constexpr const char *objMapperPath =
75 "/xyz/openbmc_project/object_mapper";
76static constexpr const char *objMapperInterface =
77 "xyz.openbmc_project.ObjectMapper";
78
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053079using namespace phosphor::logging;
80using InsufficientPermission =
81 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
82using InternalFailure =
83 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
84using InvalidArgument =
85 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
86using UserNameExists =
87 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
88using UserNameDoesNotExist =
89 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
90using UserNameGroupFail =
91 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053092using NoResource =
93 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
94
95using Argument = xyz::openbmc_project::Common::InvalidArgument;
96
97template <typename... ArgTypes>
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +053098static std::vector<std::string> executeCmd(const char *path,
99 ArgTypes &&... tArgs)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530100{
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530101 std::vector<std::string> stdOutput;
102 boost::process::ipstream stdOutStream;
103 boost::process::child execProg(path, const_cast<char *>(tArgs)...,
104 boost::process::std_out > stdOutStream);
105 std::string stdOutLine;
106
107 while (stdOutStream && std::getline(stdOutStream, stdOutLine) &&
108 !stdOutLine.empty())
109 {
110 stdOutput.emplace_back(stdOutLine);
111 }
112
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530113 execProg.wait();
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530114
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530115 int retCode = execProg.exit_code();
116 if (retCode)
117 {
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530118 log<level::ERR>("Command execution failed", entry("PATH=%d", path),
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530119 entry("RETURN_CODE:%d", retCode));
120 elog<InternalFailure>();
121 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530122
123 return stdOutput;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530124}
125
126static std::string getCSVFromVector(std::vector<std::string> vec)
127{
128 switch (vec.size())
129 {
130 case 0:
131 {
132 return "";
133 }
134 break;
135
136 case 1:
137 {
138 return std::string{vec[0]};
139 }
140 break;
141
142 default:
143 {
144 return std::accumulate(
145 std::next(vec.begin()), vec.end(), vec[0],
146 [](std::string a, std::string b) { return a + ',' + b; });
147 }
148 }
149}
150
151static bool removeStringFromCSV(std::string &csvStr, const std::string &delStr)
152{
153 std::string::size_type delStrPos = csvStr.find(delStr);
154 if (delStrPos != std::string::npos)
155 {
156 // need to also delete the comma char
157 if (delStrPos == 0)
158 {
159 csvStr.erase(delStrPos, delStr.size() + 1);
160 }
161 else
162 {
163 csvStr.erase(delStrPos - 1, delStr.size() + 1);
164 }
165 return true;
166 }
167 return false;
168}
169
170bool UserMgr::isUserExist(const std::string &userName)
171{
172 if (userName.empty())
173 {
174 log<level::ERR>("User name is empty");
175 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
176 Argument::ARGUMENT_VALUE("Null"));
177 }
178 if (usersList.find(userName) == usersList.end())
179 {
180 return false;
181 }
182 return true;
183}
184
185void UserMgr::throwForUserDoesNotExist(const std::string &userName)
186{
187 if (isUserExist(userName) == false)
188 {
189 log<level::ERR>("User does not exist",
190 entry("USER_NAME=%s", userName.c_str()));
191 elog<UserNameDoesNotExist>();
192 }
193}
194
195void UserMgr::throwForUserExists(const std::string &userName)
196{
197 if (isUserExist(userName) == true)
198 {
199 log<level::ERR>("User already exists",
200 entry("USER_NAME=%s", userName.c_str()));
201 elog<UserNameExists>();
202 }
203}
204
205void UserMgr::throwForUserNameConstraints(
206 const std::string &userName, const std::vector<std::string> &groupNames)
207{
208 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
209 groupNames.end())
210 {
211 if (userName.length() > ipmiMaxUserNameLen)
212 {
213 log<level::ERR>("IPMI user name length limitation",
214 entry("SIZE=%d", userName.length()));
215 elog<UserNameGroupFail>(
216 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
217 "IPMI length"));
218 }
219 }
220 if (userName.length() > systemMaxUserNameLen)
221 {
222 log<level::ERR>("User name length limitation",
223 entry("SIZE=%d", userName.length()));
224 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
225 Argument::ARGUMENT_VALUE("Invalid length"));
226 }
227 if (!std::regex_match(userName.c_str(),
228 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
229 {
230 log<level::ERR>("Invalid user name",
231 entry("USER_NAME=%s", userName.c_str()));
232 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
233 Argument::ARGUMENT_VALUE("Invalid data"));
234 }
235}
236
237void UserMgr::throwForMaxGrpUserCount(
238 const std::vector<std::string> &groupNames)
239{
240 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
241 groupNames.end())
242 {
243 if (getIpmiUsersCount() >= ipmiMaxUsers)
244 {
245 log<level::ERR>("IPMI user limit reached");
246 elog<NoResource>(
247 xyz::openbmc_project::User::Common::NoResource::REASON(
248 "ipmi user count reached"));
249 }
250 }
251 else
252 {
253 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
254 (maxSystemUsers - ipmiMaxUsers))
255 {
256 log<level::ERR>("Non-ipmi User limit reached");
257 elog<NoResource>(
258 xyz::openbmc_project::User::Common::NoResource::REASON(
259 "Non-ipmi user count reached"));
260 }
261 }
262 return;
263}
264
265void UserMgr::throwForInvalidPrivilege(const std::string &priv)
266{
267 if (!priv.empty() &&
268 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
269 {
270 log<level::ERR>("Invalid privilege");
271 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
272 Argument::ARGUMENT_VALUE(priv.c_str()));
273 }
274}
275
276void UserMgr::throwForInvalidGroups(const std::vector<std::string> &groupNames)
277{
278 for (auto &group : groupNames)
279 {
280 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
281 groupsMgr.end())
282 {
283 log<level::ERR>("Invalid Group Name listed");
284 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
285 Argument::ARGUMENT_VALUE(group.c_str()));
286 }
287 }
288}
289
290void UserMgr::createUser(std::string userName,
291 std::vector<std::string> groupNames, std::string priv,
292 bool enabled)
293{
294 throwForInvalidPrivilege(priv);
295 throwForInvalidGroups(groupNames);
296 // All user management lock has to be based on /etc/shadow
297 phosphor::user::shadow::Lock lock();
298 throwForUserExists(userName);
299 throwForUserNameConstraints(userName, groupNames);
300 throwForMaxGrpUserCount(groupNames);
301
302 std::string groups = getCSVFromVector(groupNames);
303 bool sshRequested = removeStringFromCSV(groups, grpSsh);
304
305 // treat privilege as a group - This is to avoid using different file to
306 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530307 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530308 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530309 if (groups.size() != 0)
310 {
311 groups += ",";
312 }
313 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530314 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530315 try
316 {
317 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530318 "-m", "-N", "-s",
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530319 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
320 (enabled ? "" : "1970-01-02"));
321 }
322 catch (const InternalFailure &e)
323 {
324 log<level::ERR>("Unable to create new user");
325 elog<InternalFailure>();
326 }
327
328 // Add the users object before sending out the signal
329 std::string userObj = std::string(usersObjPath) + "/" + userName;
330 std::sort(groupNames.begin(), groupNames.end());
331 usersList.emplace(
332 userName, std::move(std::make_unique<phosphor::user::Users>(
333 bus, userObj.c_str(), groupNames, priv, enabled, *this)));
334
335 log<level::INFO>("User created successfully",
336 entry("USER_NAME=%s", userName.c_str()));
337 return;
338}
339
340void UserMgr::deleteUser(std::string userName)
341{
342 // All user management lock has to be based on /etc/shadow
343 phosphor::user::shadow::Lock lock();
344 throwForUserDoesNotExist(userName);
345 try
346 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530347 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530348 }
349 catch (const InternalFailure &e)
350 {
351 log<level::ERR>("User delete failed",
352 entry("USER_NAME=%s", userName.c_str()));
353 elog<InternalFailure>();
354 }
355
356 usersList.erase(userName);
357
358 log<level::INFO>("User deleted successfully",
359 entry("USER_NAME=%s", userName.c_str()));
360 return;
361}
362
363void UserMgr::renameUser(std::string userName, std::string newUserName)
364{
365 // All user management lock has to be based on /etc/shadow
366 phosphor::user::shadow::Lock lock();
367 throwForUserDoesNotExist(userName);
368 throwForUserExists(newUserName);
369 throwForUserNameConstraints(newUserName,
370 usersList[userName].get()->userGroups());
371 try
372 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530373 std::string newHomeDir = "/home/" + newUserName;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530374 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530375 userName.c_str(), "-d", newHomeDir.c_str(), "-m");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530376 }
377 catch (const InternalFailure &e)
378 {
379 log<level::ERR>("User rename failed",
380 entry("USER_NAME=%s", userName.c_str()));
381 elog<InternalFailure>();
382 }
383 const auto &user = usersList[userName];
384 std::string priv = user.get()->userPrivilege();
385 std::vector<std::string> groupNames = user.get()->userGroups();
386 bool enabled = user.get()->userEnabled();
387 std::string newUserObj = std::string(usersObjPath) + "/" + newUserName;
388 // Special group 'ipmi' needs a way to identify user renamed, in order to
389 // update encrypted password. It can't rely only on InterfacesRemoved &
390 // InterfacesAdded. So first send out userRenamed signal.
391 this->userRenamed(userName, newUserName);
392 usersList.erase(userName);
393 usersList.emplace(
394 newUserName,
395 std::move(std::make_unique<phosphor::user::Users>(
396 bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
397 return;
398}
399
400void UserMgr::updateGroupsAndPriv(const std::string &userName,
401 const std::vector<std::string> &groupNames,
402 const std::string &priv)
403{
404 throwForInvalidPrivilege(priv);
405 throwForInvalidGroups(groupNames);
406 // All user management lock has to be based on /etc/shadow
407 phosphor::user::shadow::Lock lock();
408 throwForUserDoesNotExist(userName);
409 const std::vector<std::string> &oldGroupNames =
410 usersList[userName].get()->userGroups();
411 std::vector<std::string> groupDiff;
412 // Note: already dealing with sorted group lists.
413 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
414 groupNames.begin(), groupNames.end(),
415 std::back_inserter(groupDiff));
416 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
417 groupDiff.end())
418 {
419 throwForUserNameConstraints(userName, groupNames);
420 throwForMaxGrpUserCount(groupNames);
421 }
422
423 std::string groups = getCSVFromVector(groupNames);
424 bool sshRequested = removeStringFromCSV(groups, grpSsh);
425
426 // treat privilege as a group - This is to avoid using different file to
427 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530428 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530429 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530430 if (groups.size() != 0)
431 {
432 groups += ",";
433 }
434 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530435 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530436 try
437 {
438 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
439 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
440 }
441 catch (const InternalFailure &e)
442 {
443 log<level::ERR>("Unable to modify user privilege / groups");
444 elog<InternalFailure>();
445 }
446
447 log<level::INFO>("User groups / privilege updated successfully",
448 entry("USER_NAME=%s", userName.c_str()));
449 return;
450}
451
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530452uint8_t UserMgr::minPasswordLength(uint8_t value)
453{
454 if (value == AccountPolicyIface::minPasswordLength())
455 {
456 return value;
457 }
458 if (value < minPasswdLength)
459 {
460 return value;
461 }
462 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
463 std::to_string(value)) != success)
464 {
465 log<level::ERR>("Unable to set minPasswordLength");
466 elog<InternalFailure>();
467 }
468 return AccountPolicyIface::minPasswordLength(value);
469}
470
471uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
472{
473 if (value == AccountPolicyIface::rememberOldPasswordTimes())
474 {
475 return value;
476 }
477 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
478 std::to_string(value)) != success)
479 {
480 log<level::ERR>("Unable to set rememberOldPasswordTimes");
481 elog<InternalFailure>();
482 }
483 return AccountPolicyIface::rememberOldPasswordTimes(value);
484}
485
486uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
487{
488 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
489 {
490 return value;
491 }
492 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
493 std::to_string(value)) != success)
494 {
495 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
496 elog<InternalFailure>();
497 }
498 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
499}
500
501uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
502{
503 if (value == AccountPolicyIface::accountUnlockTimeout())
504 {
505 return value;
506 }
507 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
508 success)
509 {
510 log<level::ERR>("Unable to set accountUnlockTimeout");
511 elog<InternalFailure>();
512 }
513 return AccountPolicyIface::accountUnlockTimeout(value);
514}
515
516int UserMgr::getPamModuleArgValue(const std::string &moduleName,
517 const std::string &argName,
518 std::string &argValue)
519{
520 std::string fileName;
521 if (moduleName == pamTally2)
522 {
523 fileName = pamAuthConfigFile;
524 }
525 else
526 {
527 fileName = pamPasswdConfigFile;
528 }
529 std::ifstream fileToRead(fileName, std::ios::in);
530 if (!fileToRead.is_open())
531 {
532 log<level::ERR>("Failed to open pam configuration file",
533 entry("FILE_NAME=%s", fileName.c_str()));
534 return failure;
535 }
536 std::string line;
537 auto argSearch = argName + "=";
538 size_t startPos = 0;
539 size_t endPos = 0;
540 while (getline(fileToRead, line))
541 {
542 // skip comments section starting with #
543 if ((startPos = line.find('#')) != std::string::npos)
544 {
545 if (startPos == 0)
546 {
547 continue;
548 }
549 // skip comments after meaningful section and process those
550 line = line.substr(0, startPos);
551 }
552 if (line.find(moduleName) != std::string::npos)
553 {
554 if ((startPos = line.find(argSearch)) != std::string::npos)
555 {
556 if ((endPos = line.find(' ', startPos)) == std::string::npos)
557 {
558 endPos = line.size();
559 }
560 startPos += argSearch.size();
561 argValue = line.substr(startPos, endPos - startPos);
562 return success;
563 }
564 }
565 }
566 return failure;
567}
568
569int UserMgr::setPamModuleArgValue(const std::string &moduleName,
570 const std::string &argName,
571 const std::string &argValue)
572{
573 std::string fileName;
574 if (moduleName == pamTally2)
575 {
576 fileName = pamAuthConfigFile;
577 }
578 else
579 {
580 fileName = pamPasswdConfigFile;
581 }
582 std::string tmpFileName = fileName + "_tmp";
583 std::ifstream fileToRead(fileName, std::ios::in);
584 std::ofstream fileToWrite(tmpFileName, std::ios::out);
585 if (!fileToRead.is_open() || !fileToWrite.is_open())
586 {
587 log<level::ERR>("Failed to open pam configuration /tmp file",
588 entry("FILE_NAME=%s", fileName.c_str()));
589 return failure;
590 }
591 std::string line;
592 auto argSearch = argName + "=";
593 size_t startPos = 0;
594 size_t endPos = 0;
595 bool found = false;
596 while (getline(fileToRead, line))
597 {
598 // skip comments section starting with #
599 if ((startPos = line.find('#')) != std::string::npos)
600 {
601 if (startPos == 0)
602 {
603 fileToWrite << line << std::endl;
604 continue;
605 }
606 // skip comments after meaningful section and process those
607 line = line.substr(0, startPos);
608 }
609 if (line.find(moduleName) != std::string::npos)
610 {
611 if ((startPos = line.find(argSearch)) != std::string::npos)
612 {
613 if ((endPos = line.find(' ', startPos)) == std::string::npos)
614 {
615 endPos = line.size();
616 }
617 startPos += argSearch.size();
618 fileToWrite << line.substr(0, startPos) << argValue
619 << line.substr(endPos, line.size() - endPos)
620 << std::endl;
621 found = true;
622 continue;
623 }
624 }
625 fileToWrite << line << std::endl;
626 }
627 fileToWrite.close();
628 fileToRead.close();
629 if (found)
630 {
631 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
632 {
633 return success;
634 }
635 }
636 return failure;
637}
638
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530639void UserMgr::userEnable(const std::string &userName, bool enabled)
640{
641 // All user management lock has to be based on /etc/shadow
642 phosphor::user::shadow::Lock lock();
643 throwForUserDoesNotExist(userName);
644 try
645 {
646 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
647 (enabled ? "" : "1970-01-02"));
648 }
649 catch (const InternalFailure &e)
650 {
651 log<level::ERR>("Unable to modify user enabled state");
652 elog<InternalFailure>();
653 }
654
655 log<level::INFO>("User enabled/disabled state updated successfully",
656 entry("USER_NAME=%s", userName.c_str()),
657 entry("ENABLED=%d", enabled));
658 return;
659}
660
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530661/**
662 * pam_tally2 app will provide the user failure count and failure status
663 * in second line of output with words position [0] - user name,
664 * [1] - failure count, [2] - latest timestamp, [3] - failure timestamp
665 * [4] - failure app
666 **/
667
668static constexpr size_t t2UserIdx = 0;
669static constexpr size_t t2FailCntIdx = 1;
670static constexpr size_t t2OutputIndex = 1;
671
672bool UserMgr::userLockedForFailedAttempt(const std::string &userName)
673{
674 // All user management lock has to be based on /etc/shadow
675 phosphor::user::shadow::Lock lock();
676 std::vector<std::string> output;
677
678 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
679
680 std::vector<std::string> splitWords;
681 boost::algorithm::split(splitWords, output[t2OutputIndex],
682 boost::algorithm::is_any_of("\t "),
683 boost::token_compress_on);
684
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530685 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530686 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530687 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
688 uint16_t value16 = 0;
689 if (tmp > std::numeric_limits<decltype(value16)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530690 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530691 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530692 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530693 value16 = static_cast<decltype(value16)>(tmp);
694 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 &&
695 value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530696 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530697 return true; // User account is locked out
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530698 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530699 return false; // User account is un-locked
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530700 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530701 catch (const std::exception &e)
702 {
703 log<level::ERR>("Exception for userLockedForFailedAttempt",
704 entry("WHAT=%s", e.what()));
705 throw;
706 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530707}
708
709bool UserMgr::userLockedForFailedAttempt(const std::string &userName,
710 const bool &value)
711{
712 // All user management lock has to be based on /etc/shadow
713 phosphor::user::shadow::Lock lock();
714 std::vector<std::string> output;
715 if (value == true)
716 {
717 return userLockedForFailedAttempt(userName);
718 }
719 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
720
721 std::vector<std::string> splitWords;
722 boost::algorithm::split(splitWords, output[t2OutputIndex],
723 boost::algorithm::is_any_of("\t "),
724 boost::token_compress_on);
725
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530726 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530727}
728
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530729UserSSHLists UserMgr::getUserAndSshGrpList()
730{
731 // All user management lock has to be based on /etc/shadow
732 phosphor::user::shadow::Lock lock();
733
734 std::vector<std::string> userList;
735 std::vector<std::string> sshUsersList;
736 struct passwd pw, *pwp = nullptr;
737 std::array<char, 1024> buffer{};
738
739 phosphor::user::File passwd(passwdFileName, "r");
740 if ((passwd)() == NULL)
741 {
742 log<level::ERR>("Error opening the passwd file");
743 elog<InternalFailure>();
744 }
745
746 while (true)
747 {
748 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
749 &pwp);
750 if ((r != 0) || (pwp == NULL))
751 {
752 // Any error, break the loop.
753 break;
754 }
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530755 // Add all users whose UID >= 1000 and < 65534
756 // and special UID 0.
757 if ((pwp->pw_uid == 0) ||
758 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530759 {
760 std::string userName(pwp->pw_name);
761 userList.emplace_back(userName);
762
763 // ssh doesn't have separate group. Check login shell entry to
764 // get all users list which are member of ssh group.
765 std::string loginShell(pwp->pw_shell);
766 if (loginShell == "/bin/sh")
767 {
768 sshUsersList.emplace_back(userName);
769 }
770 }
771 }
772 endpwent();
773 return std::make_pair(std::move(userList), std::move(sshUsersList));
774}
775
776size_t UserMgr::getIpmiUsersCount()
777{
778 std::vector<std::string> userList = getUsersInGroup("ipmi");
779 return userList.size();
780}
781
782bool UserMgr::isUserEnabled(const std::string &userName)
783{
784 // All user management lock has to be based on /etc/shadow
785 phosphor::user::shadow::Lock lock();
786 std::array<char, 4096> buffer{};
787 struct spwd spwd;
788 struct spwd *resultPtr = nullptr;
789 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
790 buffer.max_size(), &resultPtr);
791 if (!status && (&spwd == resultPtr))
792 {
793 if (resultPtr->sp_expire >= 0)
794 {
795 return false; // user locked out
796 }
797 return true;
798 }
799 return false; // assume user is disabled for any error.
800}
801
802std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName)
803{
804 std::vector<std::string> usersInGroup;
805 // Should be more than enough to get the pwd structure.
806 std::array<char, 4096> buffer{};
807 struct group grp;
808 struct group *resultPtr = nullptr;
809
810 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
811 buffer.max_size(), &resultPtr);
812
813 if (!status && (&grp == resultPtr))
814 {
815 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
816 {
817 usersInGroup.emplace_back(*(grp.gr_mem));
818 }
819 }
820 else
821 {
822 log<level::ERR>("Group not found",
823 entry("GROUP=%s", groupName.c_str()));
824 // Don't throw error, just return empty userList - fallback
825 }
826 return usersInGroup;
827}
828
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600829DbusUserObj UserMgr::getPrivilegeMapperObject(void)
830{
831 DbusUserObj objects;
832 try
833 {
834 std::string basePath = "/xyz/openbmc_project/user/ldap";
835 std::string interface = "xyz.openbmc_project.User.PrivilegeMapper";
836
837 auto ldapMgmtService =
838 getServiceName(std::move(basePath), std::move(interface));
839
840 auto method = bus.new_method_call(
841 ldapMgmtService.c_str(), ldapMgrObjBasePath,
842 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
843
844 auto reply = bus.call(method);
845 reply.read(objects);
846 }
847 catch (const InternalFailure &e)
848 {
849 log<level::ERR>("Unable to get the User Service",
850 entry("WHAT=%s", e.what()));
851 throw;
852 }
853 catch (const sdbusplus::exception::SdBusError &e)
854 {
855 log<level::ERR>(
856 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
857 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
858 throw;
859 }
860 return objects;
861}
862
863std::string UserMgr::getLdapGroupName(const std::string &userName)
864{
865 struct passwd pwd
866 {
867 };
868 struct passwd *pwdPtr = nullptr;
869 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
870 if (buflen < -1)
871 {
872 // Use a default size if there is no hard limit suggested by sysconf()
873 buflen = 1024;
874 }
875 std::vector<char> buffer(buflen);
876 gid_t gid = 0;
877
878 auto status =
879 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
880 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
881 // If no matching password record was found, these functions return 0
882 // and store NULL in *pwdPtr
883 if (!status && (&pwd == pwdPtr))
884 {
885 gid = pwd.pw_gid;
886 }
887 else
888 {
889 log<level::ERR>("User does not exist",
890 entry("USER_NAME=%s", userName.c_str()));
891 elog<UserNameDoesNotExist>();
892 }
893
894 struct group *groups = nullptr;
895 std::string ldapGroupName;
896
897 while ((groups = getgrent()) != NULL)
898 {
899 if (groups->gr_gid == gid)
900 {
901 ldapGroupName = groups->gr_name;
902 break;
903 }
904 }
905 // Call endgrent() to close the group database.
906 endgrent();
907
908 return ldapGroupName;
909}
910
911std::string UserMgr::getServiceName(std::string &&path, std::string &&intf)
912{
913 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
914 objMapperInterface, "GetObject");
915
916 mapperCall.append(std::move(path));
917 mapperCall.append(std::vector<std::string>({std::move(intf)}));
918
919 auto mapperResponseMsg = bus.call(mapperCall);
920
921 if (mapperResponseMsg.is_method_error())
922 {
923 log<level::ERR>("Error in mapper call");
924 elog<InternalFailure>();
925 }
926
927 std::map<std::string, std::vector<std::string>> mapperResponse;
928 mapperResponseMsg.read(mapperResponse);
929
930 if (mapperResponse.begin() == mapperResponse.end())
931 {
932 log<level::ERR>("Invalid response from mapper");
933 elog<InternalFailure>();
934 }
935
936 return mapperResponse.begin()->first;
937}
938
939UserInfoMap UserMgr::getUserInfo(std::string userName)
940{
941 UserInfoMap userInfo;
942 // Check whether the given user is local user or not.
943 if (isUserExist(userName) == true)
944 {
945 const auto &user = usersList[userName];
946 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
947 userInfo.emplace("UserGroups", user.get()->userGroups());
948 userInfo.emplace("UserEnabled", user.get()->userEnabled());
949 userInfo.emplace("UserLockedForFailedAttempt",
950 user.get()->userLockedForFailedAttempt());
951 userInfo.emplace("RemoteUser", false);
952 }
953 else
954 {
955 std::string ldapGroupName = getLdapGroupName(userName);
956 if (ldapGroupName.empty())
957 {
958 log<level::ERR>("Unable to get group name",
959 entry("USER_NAME=%s", userName.c_str()));
960 elog<InternalFailure>();
961 }
962
963 DbusUserObj objects = getPrivilegeMapperObject();
964
965 std::string privilege;
966 std::string groupName;
967
968 try
969 {
970 for (const auto &objpath : objects)
971 {
972 auto iter = objpath.second.find(
973 "xyz.openbmc_project.User.PrivilegeMapperEntry");
974 if (iter == objpath.second.end())
975 {
976 log<level::ERR>(
977 "Error in finding privilege mapper entry interface");
978 elog<InternalFailure>();
979 }
980 for (const auto &property : iter->second)
981 {
982 auto value =
983 sdbusplus::message::variant_ns::get<std::string>(
984 property.second);
985 if (property.first == "GroupName")
986 {
987 groupName = value;
988 }
989 else if (property.first == "Privilege")
990 {
991 privilege = value;
992 }
993 if (groupName == ldapGroupName)
994 {
995 userInfo["UserPrivilege"] = privilege;
996 }
997 }
998 }
999 auto priv = std::get<std::string>(userInfo["UserPrivilege"]);
1000 if (priv.empty())
1001 {
1002 log<level::ERR>("LDAP group privilege mapping does not exist");
1003 }
1004 }
1005 catch (const std::bad_variant_access &e)
1006 {
1007 log<level::ERR>("Error while accessing variant",
1008 entry("WHAT=%s", e.what()));
1009 elog<InternalFailure>();
1010 }
1011 userInfo.emplace("RemoteUser", true);
1012 }
1013
1014 return userInfo;
1015}
1016
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301017void UserMgr::initUserObjects(void)
1018{
1019 // All user management lock has to be based on /etc/shadow
1020 phosphor::user::shadow::Lock lock();
1021 std::vector<std::string> userNameList;
1022 std::vector<std::string> sshGrpUsersList;
1023 UserSSHLists userSSHLists = getUserAndSshGrpList();
1024 userNameList = std::move(userSSHLists.first);
1025 sshGrpUsersList = std::move(userSSHLists.second);
1026
1027 if (!userNameList.empty())
1028 {
1029 std::map<std::string, std::vector<std::string>> groupLists;
1030 for (auto &grp : groupsMgr)
1031 {
1032 if (grp == grpSsh)
1033 {
1034 groupLists.emplace(grp, sshGrpUsersList);
1035 }
1036 else
1037 {
1038 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1039 groupLists.emplace(grp, grpUsersList);
1040 }
1041 }
1042 for (auto &grp : privMgr)
1043 {
1044 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1045 groupLists.emplace(grp, grpUsersList);
1046 }
1047
1048 for (auto &user : userNameList)
1049 {
1050 std::vector<std::string> userGroups;
1051 std::string userPriv;
1052 for (const auto &grp : groupLists)
1053 {
1054 std::vector<std::string> tempGrp = grp.second;
1055 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1056 tempGrp.end())
1057 {
1058 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1059 privMgr.end())
1060 {
1061 userPriv = grp.first;
1062 }
1063 else
1064 {
1065 userGroups.emplace_back(grp.first);
1066 }
1067 }
1068 }
1069 // Add user objects to the Users path.
1070 auto objPath = std::string(usersObjPath) + "/" + user;
1071 std::sort(userGroups.begin(), userGroups.end());
1072 usersList.emplace(user,
1073 std::move(std::make_unique<phosphor::user::Users>(
1074 bus, objPath.c_str(), userGroups, userPriv,
1075 isUserEnabled(user), *this)));
1076 }
1077 }
1078}
1079
1080UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) :
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301081 UserMgrIface(bus, path), AccountPolicyIface(bus, path), bus(bus), path(path)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301082{
1083 UserMgrIface::allPrivileges(privMgr);
1084 std::sort(groupsMgr.begin(), groupsMgr.end());
1085 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301086 std::string valueStr;
1087 auto value = minPasswdLength;
1088 unsigned long tmp = 0;
1089 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1090 success)
1091 {
1092 AccountPolicyIface::minPasswordLength(minPasswdLength);
1093 }
1094 else
1095 {
1096 try
1097 {
1098 tmp = std::stoul(valueStr, nullptr);
1099 if (tmp > std::numeric_limits<decltype(value)>::max())
1100 {
1101 throw std::out_of_range("Out of range");
1102 }
1103 value = static_cast<decltype(value)>(tmp);
1104 }
1105 catch (const std::exception &e)
1106 {
1107 log<level::ERR>("Exception for MinPasswordLength",
1108 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001109 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301110 }
1111 AccountPolicyIface::minPasswordLength(value);
1112 }
1113 valueStr.clear();
1114 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1115 success)
1116 {
1117 AccountPolicyIface::rememberOldPasswordTimes(0);
1118 }
1119 else
1120 {
1121 value = 0;
1122 try
1123 {
1124 tmp = std::stoul(valueStr, nullptr);
1125 if (tmp > std::numeric_limits<decltype(value)>::max())
1126 {
1127 throw std::out_of_range("Out of range");
1128 }
1129 value = static_cast<decltype(value)>(tmp);
1130 }
1131 catch (const std::exception &e)
1132 {
1133 log<level::ERR>("Exception for RememberOldPasswordTimes",
1134 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001135 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301136 }
1137 AccountPolicyIface::rememberOldPasswordTimes(value);
1138 }
1139 valueStr.clear();
1140 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1141 {
1142 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1143 }
1144 else
1145 {
1146 uint16_t value16 = 0;
1147 try
1148 {
1149 tmp = std::stoul(valueStr, nullptr);
1150 if (tmp > std::numeric_limits<decltype(value16)>::max())
1151 {
1152 throw std::out_of_range("Out of range");
1153 }
1154 value16 = static_cast<decltype(value16)>(tmp);
1155 }
1156 catch (const std::exception &e)
1157 {
1158 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1159 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001160 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301161 }
1162 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1163 }
1164 valueStr.clear();
1165 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1166 {
1167 AccountPolicyIface::accountUnlockTimeout(0);
1168 }
1169 else
1170 {
1171 uint32_t value32 = 0;
1172 try
1173 {
1174 tmp = std::stoul(valueStr, nullptr);
1175 if (tmp > std::numeric_limits<decltype(value32)>::max())
1176 {
1177 throw std::out_of_range("Out of range");
1178 }
1179 value32 = static_cast<decltype(value32)>(tmp);
1180 }
1181 catch (const std::exception &e)
1182 {
1183 log<level::ERR>("Exception for AccountUnlockTimeout",
1184 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001185 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301186 }
1187 AccountPolicyIface::accountUnlockTimeout(value32);
1188 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301189 initUserObjects();
1190}
1191
1192} // namespace user
1193} // namespace phosphor