blob: 17146e657f562ff2badaae9122582ddea9d57a22 [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 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500834 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
835 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600836
837 auto ldapMgmtService =
838 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600839 auto method = bus.new_method_call(
840 ldapMgmtService.c_str(), ldapMgrObjBasePath,
841 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
842
843 auto reply = bus.call(method);
844 reply.read(objects);
845 }
846 catch (const InternalFailure &e)
847 {
848 log<level::ERR>("Unable to get the User Service",
849 entry("WHAT=%s", e.what()));
850 throw;
851 }
852 catch (const sdbusplus::exception::SdBusError &e)
853 {
854 log<level::ERR>(
855 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
856 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
857 throw;
858 }
859 return objects;
860}
861
862std::string UserMgr::getLdapGroupName(const std::string &userName)
863{
864 struct passwd pwd
865 {
866 };
867 struct passwd *pwdPtr = nullptr;
868 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
869 if (buflen < -1)
870 {
871 // Use a default size if there is no hard limit suggested by sysconf()
872 buflen = 1024;
873 }
874 std::vector<char> buffer(buflen);
875 gid_t gid = 0;
876
877 auto status =
878 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
879 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
880 // If no matching password record was found, these functions return 0
881 // and store NULL in *pwdPtr
882 if (!status && (&pwd == pwdPtr))
883 {
884 gid = pwd.pw_gid;
885 }
886 else
887 {
888 log<level::ERR>("User does not exist",
889 entry("USER_NAME=%s", userName.c_str()));
890 elog<UserNameDoesNotExist>();
891 }
892
893 struct group *groups = nullptr;
894 std::string ldapGroupName;
895
896 while ((groups = getgrent()) != NULL)
897 {
898 if (groups->gr_gid == gid)
899 {
900 ldapGroupName = groups->gr_name;
901 break;
902 }
903 }
904 // Call endgrent() to close the group database.
905 endgrent();
906
907 return ldapGroupName;
908}
909
910std::string UserMgr::getServiceName(std::string &&path, std::string &&intf)
911{
912 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
913 objMapperInterface, "GetObject");
914
915 mapperCall.append(std::move(path));
916 mapperCall.append(std::vector<std::string>({std::move(intf)}));
917
918 auto mapperResponseMsg = bus.call(mapperCall);
919
920 if (mapperResponseMsg.is_method_error())
921 {
922 log<level::ERR>("Error in mapper call");
923 elog<InternalFailure>();
924 }
925
926 std::map<std::string, std::vector<std::string>> mapperResponse;
927 mapperResponseMsg.read(mapperResponse);
928
929 if (mapperResponse.begin() == mapperResponse.end())
930 {
931 log<level::ERR>("Invalid response from mapper");
932 elog<InternalFailure>();
933 }
934
935 return mapperResponse.begin()->first;
936}
937
938UserInfoMap UserMgr::getUserInfo(std::string userName)
939{
940 UserInfoMap userInfo;
941 // Check whether the given user is local user or not.
942 if (isUserExist(userName) == true)
943 {
944 const auto &user = usersList[userName];
945 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
946 userInfo.emplace("UserGroups", user.get()->userGroups());
947 userInfo.emplace("UserEnabled", user.get()->userEnabled());
948 userInfo.emplace("UserLockedForFailedAttempt",
949 user.get()->userLockedForFailedAttempt());
950 userInfo.emplace("RemoteUser", false);
951 }
952 else
953 {
954 std::string ldapGroupName = getLdapGroupName(userName);
955 if (ldapGroupName.empty())
956 {
957 log<level::ERR>("Unable to get group name",
958 entry("USER_NAME=%s", userName.c_str()));
959 elog<InternalFailure>();
960 }
961
962 DbusUserObj objects = getPrivilegeMapperObject();
963
964 std::string privilege;
965 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -0500966 std::string ldapConfigPath;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600967
968 try
969 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500970 for (const auto &obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600971 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500972 for (const auto &interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600973 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500974 if ((interface.first ==
975 "xyz.openbmc_project.Object.Enable"))
976 {
977 for (const auto &property : interface.second)
978 {
979 auto value =
980 sdbusplus::message::variant_ns::get<bool>(
981 property.second);
982 if ((property.first == "Enabled") &&
983 (value == true))
984 {
985 ldapConfigPath = obj.first;
986 break;
987 }
988 }
989 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600990 }
Ravi Teja5fe724a2019-05-07 05:14:42 -0500991 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600992 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500993 break;
994 }
995 }
996
997 if (ldapConfigPath.empty())
998 {
999 return userInfo;
1000 }
1001
1002 for (const auto &obj : objects)
1003 {
1004 for (const auto &interface : obj.second)
1005 {
1006 if ((interface.first ==
1007 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1008 (obj.first.str.find(ldapConfigPath) !=
1009 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001010 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001011
1012 for (const auto &property : interface.second)
1013 {
1014 auto value = sdbusplus::message::variant_ns::get<
1015 std::string>(property.second);
1016 if (property.first == "GroupName")
1017 {
1018 groupName = value;
1019 }
1020 else if (property.first == "Privilege")
1021 {
1022 privilege = value;
1023 }
1024 if (groupName == ldapGroupName)
1025 {
1026 userInfo["UserPrivilege"] = privilege;
1027 }
1028 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001029 }
1030 }
1031 }
1032 auto priv = std::get<std::string>(userInfo["UserPrivilege"]);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001033
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001034 if (priv.empty())
1035 {
1036 log<level::ERR>("LDAP group privilege mapping does not exist");
1037 }
1038 }
1039 catch (const std::bad_variant_access &e)
1040 {
1041 log<level::ERR>("Error while accessing variant",
1042 entry("WHAT=%s", e.what()));
1043 elog<InternalFailure>();
1044 }
1045 userInfo.emplace("RemoteUser", true);
1046 }
1047
1048 return userInfo;
1049}
1050
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301051void UserMgr::initUserObjects(void)
1052{
1053 // All user management lock has to be based on /etc/shadow
1054 phosphor::user::shadow::Lock lock();
1055 std::vector<std::string> userNameList;
1056 std::vector<std::string> sshGrpUsersList;
1057 UserSSHLists userSSHLists = getUserAndSshGrpList();
1058 userNameList = std::move(userSSHLists.first);
1059 sshGrpUsersList = std::move(userSSHLists.second);
1060
1061 if (!userNameList.empty())
1062 {
1063 std::map<std::string, std::vector<std::string>> groupLists;
1064 for (auto &grp : groupsMgr)
1065 {
1066 if (grp == grpSsh)
1067 {
1068 groupLists.emplace(grp, sshGrpUsersList);
1069 }
1070 else
1071 {
1072 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1073 groupLists.emplace(grp, grpUsersList);
1074 }
1075 }
1076 for (auto &grp : privMgr)
1077 {
1078 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1079 groupLists.emplace(grp, grpUsersList);
1080 }
1081
1082 for (auto &user : userNameList)
1083 {
1084 std::vector<std::string> userGroups;
1085 std::string userPriv;
1086 for (const auto &grp : groupLists)
1087 {
1088 std::vector<std::string> tempGrp = grp.second;
1089 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1090 tempGrp.end())
1091 {
1092 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1093 privMgr.end())
1094 {
1095 userPriv = grp.first;
1096 }
1097 else
1098 {
1099 userGroups.emplace_back(grp.first);
1100 }
1101 }
1102 }
1103 // Add user objects to the Users path.
1104 auto objPath = std::string(usersObjPath) + "/" + user;
1105 std::sort(userGroups.begin(), userGroups.end());
1106 usersList.emplace(user,
1107 std::move(std::make_unique<phosphor::user::Users>(
1108 bus, objPath.c_str(), userGroups, userPriv,
1109 isUserEnabled(user), *this)));
1110 }
1111 }
1112}
1113
1114UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) :
Ratan Gupta1af12232018-11-03 00:35:38 +05301115 Ifaces(bus, path, true), bus(bus), path(path)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301116{
1117 UserMgrIface::allPrivileges(privMgr);
1118 std::sort(groupsMgr.begin(), groupsMgr.end());
1119 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301120 std::string valueStr;
1121 auto value = minPasswdLength;
1122 unsigned long tmp = 0;
1123 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1124 success)
1125 {
1126 AccountPolicyIface::minPasswordLength(minPasswdLength);
1127 }
1128 else
1129 {
1130 try
1131 {
1132 tmp = std::stoul(valueStr, nullptr);
1133 if (tmp > std::numeric_limits<decltype(value)>::max())
1134 {
1135 throw std::out_of_range("Out of range");
1136 }
1137 value = static_cast<decltype(value)>(tmp);
1138 }
1139 catch (const std::exception &e)
1140 {
1141 log<level::ERR>("Exception for MinPasswordLength",
1142 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001143 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301144 }
1145 AccountPolicyIface::minPasswordLength(value);
1146 }
1147 valueStr.clear();
1148 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1149 success)
1150 {
1151 AccountPolicyIface::rememberOldPasswordTimes(0);
1152 }
1153 else
1154 {
1155 value = 0;
1156 try
1157 {
1158 tmp = std::stoul(valueStr, nullptr);
1159 if (tmp > std::numeric_limits<decltype(value)>::max())
1160 {
1161 throw std::out_of_range("Out of range");
1162 }
1163 value = static_cast<decltype(value)>(tmp);
1164 }
1165 catch (const std::exception &e)
1166 {
1167 log<level::ERR>("Exception for RememberOldPasswordTimes",
1168 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001169 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301170 }
1171 AccountPolicyIface::rememberOldPasswordTimes(value);
1172 }
1173 valueStr.clear();
1174 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1175 {
1176 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1177 }
1178 else
1179 {
1180 uint16_t value16 = 0;
1181 try
1182 {
1183 tmp = std::stoul(valueStr, nullptr);
1184 if (tmp > std::numeric_limits<decltype(value16)>::max())
1185 {
1186 throw std::out_of_range("Out of range");
1187 }
1188 value16 = static_cast<decltype(value16)>(tmp);
1189 }
1190 catch (const std::exception &e)
1191 {
1192 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1193 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001194 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301195 }
1196 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1197 }
1198 valueStr.clear();
1199 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1200 {
1201 AccountPolicyIface::accountUnlockTimeout(0);
1202 }
1203 else
1204 {
1205 uint32_t value32 = 0;
1206 try
1207 {
1208 tmp = std::stoul(valueStr, nullptr);
1209 if (tmp > std::numeric_limits<decltype(value32)>::max())
1210 {
1211 throw std::out_of_range("Out of range");
1212 }
1213 value32 = static_cast<decltype(value32)>(tmp);
1214 }
1215 catch (const std::exception &e)
1216 {
1217 log<level::ERR>("Exception for AccountUnlockTimeout",
1218 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001219 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301220 }
1221 AccountPolicyIface::accountUnlockTimeout(value32);
1222 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301223 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301224
1225 // emit the signal
1226 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301227}
1228
1229} // namespace user
1230} // namespace phosphor