blob: 1cbd43a7aff8eec200ef3c43835e5d84bf31b646 [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
67using namespace phosphor::logging;
68using InsufficientPermission =
69 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
70using InternalFailure =
71 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
72using InvalidArgument =
73 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
74using UserNameExists =
75 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
76using UserNameDoesNotExist =
77 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
78using UserNameGroupFail =
79 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
80
81using NoResource =
82 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
83
84using Argument = xyz::openbmc_project::Common::InvalidArgument;
85
86template <typename... ArgTypes>
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +053087static std::vector<std::string> executeCmd(const char *path,
88 ArgTypes &&... tArgs)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053089{
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +053090 std::vector<std::string> stdOutput;
91 boost::process::ipstream stdOutStream;
92 boost::process::child execProg(path, const_cast<char *>(tArgs)...,
93 boost::process::std_out > stdOutStream);
94 std::string stdOutLine;
95
96 while (stdOutStream && std::getline(stdOutStream, stdOutLine) &&
97 !stdOutLine.empty())
98 {
99 stdOutput.emplace_back(stdOutLine);
100 }
101
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530102 execProg.wait();
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530103
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530104 int retCode = execProg.exit_code();
105 if (retCode)
106 {
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530107 log<level::ERR>("Command execution failed", entry("PATH=%d", path),
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530108 entry("RETURN_CODE:%d", retCode));
109 elog<InternalFailure>();
110 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530111
112 return stdOutput;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530113}
114
115static std::string getCSVFromVector(std::vector<std::string> vec)
116{
117 switch (vec.size())
118 {
119 case 0:
120 {
121 return "";
122 }
123 break;
124
125 case 1:
126 {
127 return std::string{vec[0]};
128 }
129 break;
130
131 default:
132 {
133 return std::accumulate(
134 std::next(vec.begin()), vec.end(), vec[0],
135 [](std::string a, std::string b) { return a + ',' + b; });
136 }
137 }
138}
139
140static bool removeStringFromCSV(std::string &csvStr, const std::string &delStr)
141{
142 std::string::size_type delStrPos = csvStr.find(delStr);
143 if (delStrPos != std::string::npos)
144 {
145 // need to also delete the comma char
146 if (delStrPos == 0)
147 {
148 csvStr.erase(delStrPos, delStr.size() + 1);
149 }
150 else
151 {
152 csvStr.erase(delStrPos - 1, delStr.size() + 1);
153 }
154 return true;
155 }
156 return false;
157}
158
159bool UserMgr::isUserExist(const std::string &userName)
160{
161 if (userName.empty())
162 {
163 log<level::ERR>("User name is empty");
164 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
165 Argument::ARGUMENT_VALUE("Null"));
166 }
167 if (usersList.find(userName) == usersList.end())
168 {
169 return false;
170 }
171 return true;
172}
173
174void UserMgr::throwForUserDoesNotExist(const std::string &userName)
175{
176 if (isUserExist(userName) == false)
177 {
178 log<level::ERR>("User does not exist",
179 entry("USER_NAME=%s", userName.c_str()));
180 elog<UserNameDoesNotExist>();
181 }
182}
183
184void UserMgr::throwForUserExists(const std::string &userName)
185{
186 if (isUserExist(userName) == true)
187 {
188 log<level::ERR>("User already exists",
189 entry("USER_NAME=%s", userName.c_str()));
190 elog<UserNameExists>();
191 }
192}
193
194void UserMgr::throwForUserNameConstraints(
195 const std::string &userName, const std::vector<std::string> &groupNames)
196{
197 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
198 groupNames.end())
199 {
200 if (userName.length() > ipmiMaxUserNameLen)
201 {
202 log<level::ERR>("IPMI user name length limitation",
203 entry("SIZE=%d", userName.length()));
204 elog<UserNameGroupFail>(
205 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
206 "IPMI length"));
207 }
208 }
209 if (userName.length() > systemMaxUserNameLen)
210 {
211 log<level::ERR>("User name length limitation",
212 entry("SIZE=%d", userName.length()));
213 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
214 Argument::ARGUMENT_VALUE("Invalid length"));
215 }
216 if (!std::regex_match(userName.c_str(),
217 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
218 {
219 log<level::ERR>("Invalid user name",
220 entry("USER_NAME=%s", userName.c_str()));
221 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
222 Argument::ARGUMENT_VALUE("Invalid data"));
223 }
224}
225
226void UserMgr::throwForMaxGrpUserCount(
227 const std::vector<std::string> &groupNames)
228{
229 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
230 groupNames.end())
231 {
232 if (getIpmiUsersCount() >= ipmiMaxUsers)
233 {
234 log<level::ERR>("IPMI user limit reached");
235 elog<NoResource>(
236 xyz::openbmc_project::User::Common::NoResource::REASON(
237 "ipmi user count reached"));
238 }
239 }
240 else
241 {
242 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
243 (maxSystemUsers - ipmiMaxUsers))
244 {
245 log<level::ERR>("Non-ipmi User limit reached");
246 elog<NoResource>(
247 xyz::openbmc_project::User::Common::NoResource::REASON(
248 "Non-ipmi user count reached"));
249 }
250 }
251 return;
252}
253
254void UserMgr::throwForInvalidPrivilege(const std::string &priv)
255{
256 if (!priv.empty() &&
257 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
258 {
259 log<level::ERR>("Invalid privilege");
260 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
261 Argument::ARGUMENT_VALUE(priv.c_str()));
262 }
263}
264
265void UserMgr::throwForInvalidGroups(const std::vector<std::string> &groupNames)
266{
267 for (auto &group : groupNames)
268 {
269 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
270 groupsMgr.end())
271 {
272 log<level::ERR>("Invalid Group Name listed");
273 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
274 Argument::ARGUMENT_VALUE(group.c_str()));
275 }
276 }
277}
278
279void UserMgr::createUser(std::string userName,
280 std::vector<std::string> groupNames, std::string priv,
281 bool enabled)
282{
283 throwForInvalidPrivilege(priv);
284 throwForInvalidGroups(groupNames);
285 // All user management lock has to be based on /etc/shadow
286 phosphor::user::shadow::Lock lock();
287 throwForUserExists(userName);
288 throwForUserNameConstraints(userName, groupNames);
289 throwForMaxGrpUserCount(groupNames);
290
291 std::string groups = getCSVFromVector(groupNames);
292 bool sshRequested = removeStringFromCSV(groups, grpSsh);
293
294 // treat privilege as a group - This is to avoid using different file to
295 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530296 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530297 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530298 if (groups.size() != 0)
299 {
300 groups += ",";
301 }
302 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530303 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530304 try
305 {
306 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530307 "-m", "-N", "-s",
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530308 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
309 (enabled ? "" : "1970-01-02"));
310 }
311 catch (const InternalFailure &e)
312 {
313 log<level::ERR>("Unable to create new user");
314 elog<InternalFailure>();
315 }
316
317 // Add the users object before sending out the signal
318 std::string userObj = std::string(usersObjPath) + "/" + userName;
319 std::sort(groupNames.begin(), groupNames.end());
320 usersList.emplace(
321 userName, std::move(std::make_unique<phosphor::user::Users>(
322 bus, userObj.c_str(), groupNames, priv, enabled, *this)));
323
324 log<level::INFO>("User created successfully",
325 entry("USER_NAME=%s", userName.c_str()));
326 return;
327}
328
329void UserMgr::deleteUser(std::string userName)
330{
331 // All user management lock has to be based on /etc/shadow
332 phosphor::user::shadow::Lock lock();
333 throwForUserDoesNotExist(userName);
334 try
335 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530336 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530337 }
338 catch (const InternalFailure &e)
339 {
340 log<level::ERR>("User delete failed",
341 entry("USER_NAME=%s", userName.c_str()));
342 elog<InternalFailure>();
343 }
344
345 usersList.erase(userName);
346
347 log<level::INFO>("User deleted successfully",
348 entry("USER_NAME=%s", userName.c_str()));
349 return;
350}
351
352void UserMgr::renameUser(std::string userName, std::string newUserName)
353{
354 // All user management lock has to be based on /etc/shadow
355 phosphor::user::shadow::Lock lock();
356 throwForUserDoesNotExist(userName);
357 throwForUserExists(newUserName);
358 throwForUserNameConstraints(newUserName,
359 usersList[userName].get()->userGroups());
360 try
361 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530362 std::string newHomeDir = "/home/" + newUserName;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530363 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530364 userName.c_str(), "-d", newHomeDir.c_str(), "-m");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530365 }
366 catch (const InternalFailure &e)
367 {
368 log<level::ERR>("User rename failed",
369 entry("USER_NAME=%s", userName.c_str()));
370 elog<InternalFailure>();
371 }
372 const auto &user = usersList[userName];
373 std::string priv = user.get()->userPrivilege();
374 std::vector<std::string> groupNames = user.get()->userGroups();
375 bool enabled = user.get()->userEnabled();
376 std::string newUserObj = std::string(usersObjPath) + "/" + newUserName;
377 // Special group 'ipmi' needs a way to identify user renamed, in order to
378 // update encrypted password. It can't rely only on InterfacesRemoved &
379 // InterfacesAdded. So first send out userRenamed signal.
380 this->userRenamed(userName, newUserName);
381 usersList.erase(userName);
382 usersList.emplace(
383 newUserName,
384 std::move(std::make_unique<phosphor::user::Users>(
385 bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
386 return;
387}
388
389void UserMgr::updateGroupsAndPriv(const std::string &userName,
390 const std::vector<std::string> &groupNames,
391 const std::string &priv)
392{
393 throwForInvalidPrivilege(priv);
394 throwForInvalidGroups(groupNames);
395 // All user management lock has to be based on /etc/shadow
396 phosphor::user::shadow::Lock lock();
397 throwForUserDoesNotExist(userName);
398 const std::vector<std::string> &oldGroupNames =
399 usersList[userName].get()->userGroups();
400 std::vector<std::string> groupDiff;
401 // Note: already dealing with sorted group lists.
402 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
403 groupNames.begin(), groupNames.end(),
404 std::back_inserter(groupDiff));
405 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
406 groupDiff.end())
407 {
408 throwForUserNameConstraints(userName, groupNames);
409 throwForMaxGrpUserCount(groupNames);
410 }
411
412 std::string groups = getCSVFromVector(groupNames);
413 bool sshRequested = removeStringFromCSV(groups, grpSsh);
414
415 // treat privilege as a group - This is to avoid using different file to
416 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530417 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530418 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530419 if (groups.size() != 0)
420 {
421 groups += ",";
422 }
423 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530424 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530425 try
426 {
427 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
428 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
429 }
430 catch (const InternalFailure &e)
431 {
432 log<level::ERR>("Unable to modify user privilege / groups");
433 elog<InternalFailure>();
434 }
435
436 log<level::INFO>("User groups / privilege updated successfully",
437 entry("USER_NAME=%s", userName.c_str()));
438 return;
439}
440
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530441uint8_t UserMgr::minPasswordLength(uint8_t value)
442{
443 if (value == AccountPolicyIface::minPasswordLength())
444 {
445 return value;
446 }
447 if (value < minPasswdLength)
448 {
449 return value;
450 }
451 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
452 std::to_string(value)) != success)
453 {
454 log<level::ERR>("Unable to set minPasswordLength");
455 elog<InternalFailure>();
456 }
457 return AccountPolicyIface::minPasswordLength(value);
458}
459
460uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
461{
462 if (value == AccountPolicyIface::rememberOldPasswordTimes())
463 {
464 return value;
465 }
466 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
467 std::to_string(value)) != success)
468 {
469 log<level::ERR>("Unable to set rememberOldPasswordTimes");
470 elog<InternalFailure>();
471 }
472 return AccountPolicyIface::rememberOldPasswordTimes(value);
473}
474
475uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
476{
477 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
478 {
479 return value;
480 }
481 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
482 std::to_string(value)) != success)
483 {
484 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
485 elog<InternalFailure>();
486 }
487 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
488}
489
490uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
491{
492 if (value == AccountPolicyIface::accountUnlockTimeout())
493 {
494 return value;
495 }
496 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
497 success)
498 {
499 log<level::ERR>("Unable to set accountUnlockTimeout");
500 elog<InternalFailure>();
501 }
502 return AccountPolicyIface::accountUnlockTimeout(value);
503}
504
505int UserMgr::getPamModuleArgValue(const std::string &moduleName,
506 const std::string &argName,
507 std::string &argValue)
508{
509 std::string fileName;
510 if (moduleName == pamTally2)
511 {
512 fileName = pamAuthConfigFile;
513 }
514 else
515 {
516 fileName = pamPasswdConfigFile;
517 }
518 std::ifstream fileToRead(fileName, std::ios::in);
519 if (!fileToRead.is_open())
520 {
521 log<level::ERR>("Failed to open pam configuration file",
522 entry("FILE_NAME=%s", fileName.c_str()));
523 return failure;
524 }
525 std::string line;
526 auto argSearch = argName + "=";
527 size_t startPos = 0;
528 size_t endPos = 0;
529 while (getline(fileToRead, line))
530 {
531 // skip comments section starting with #
532 if ((startPos = line.find('#')) != std::string::npos)
533 {
534 if (startPos == 0)
535 {
536 continue;
537 }
538 // skip comments after meaningful section and process those
539 line = line.substr(0, startPos);
540 }
541 if (line.find(moduleName) != std::string::npos)
542 {
543 if ((startPos = line.find(argSearch)) != std::string::npos)
544 {
545 if ((endPos = line.find(' ', startPos)) == std::string::npos)
546 {
547 endPos = line.size();
548 }
549 startPos += argSearch.size();
550 argValue = line.substr(startPos, endPos - startPos);
551 return success;
552 }
553 }
554 }
555 return failure;
556}
557
558int UserMgr::setPamModuleArgValue(const std::string &moduleName,
559 const std::string &argName,
560 const std::string &argValue)
561{
562 std::string fileName;
563 if (moduleName == pamTally2)
564 {
565 fileName = pamAuthConfigFile;
566 }
567 else
568 {
569 fileName = pamPasswdConfigFile;
570 }
571 std::string tmpFileName = fileName + "_tmp";
572 std::ifstream fileToRead(fileName, std::ios::in);
573 std::ofstream fileToWrite(tmpFileName, std::ios::out);
574 if (!fileToRead.is_open() || !fileToWrite.is_open())
575 {
576 log<level::ERR>("Failed to open pam configuration /tmp file",
577 entry("FILE_NAME=%s", fileName.c_str()));
578 return failure;
579 }
580 std::string line;
581 auto argSearch = argName + "=";
582 size_t startPos = 0;
583 size_t endPos = 0;
584 bool found = false;
585 while (getline(fileToRead, line))
586 {
587 // skip comments section starting with #
588 if ((startPos = line.find('#')) != std::string::npos)
589 {
590 if (startPos == 0)
591 {
592 fileToWrite << line << std::endl;
593 continue;
594 }
595 // skip comments after meaningful section and process those
596 line = line.substr(0, startPos);
597 }
598 if (line.find(moduleName) != std::string::npos)
599 {
600 if ((startPos = line.find(argSearch)) != std::string::npos)
601 {
602 if ((endPos = line.find(' ', startPos)) == std::string::npos)
603 {
604 endPos = line.size();
605 }
606 startPos += argSearch.size();
607 fileToWrite << line.substr(0, startPos) << argValue
608 << line.substr(endPos, line.size() - endPos)
609 << std::endl;
610 found = true;
611 continue;
612 }
613 }
614 fileToWrite << line << std::endl;
615 }
616 fileToWrite.close();
617 fileToRead.close();
618 if (found)
619 {
620 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
621 {
622 return success;
623 }
624 }
625 return failure;
626}
627
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530628void UserMgr::userEnable(const std::string &userName, bool enabled)
629{
630 // All user management lock has to be based on /etc/shadow
631 phosphor::user::shadow::Lock lock();
632 throwForUserDoesNotExist(userName);
633 try
634 {
635 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
636 (enabled ? "" : "1970-01-02"));
637 }
638 catch (const InternalFailure &e)
639 {
640 log<level::ERR>("Unable to modify user enabled state");
641 elog<InternalFailure>();
642 }
643
644 log<level::INFO>("User enabled/disabled state updated successfully",
645 entry("USER_NAME=%s", userName.c_str()),
646 entry("ENABLED=%d", enabled));
647 return;
648}
649
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530650/**
651 * pam_tally2 app will provide the user failure count and failure status
652 * in second line of output with words position [0] - user name,
653 * [1] - failure count, [2] - latest timestamp, [3] - failure timestamp
654 * [4] - failure app
655 **/
656
657static constexpr size_t t2UserIdx = 0;
658static constexpr size_t t2FailCntIdx = 1;
659static constexpr size_t t2OutputIndex = 1;
660
661bool UserMgr::userLockedForFailedAttempt(const std::string &userName)
662{
663 // All user management lock has to be based on /etc/shadow
664 phosphor::user::shadow::Lock lock();
665 std::vector<std::string> output;
666
667 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
668
669 std::vector<std::string> splitWords;
670 boost::algorithm::split(splitWords, output[t2OutputIndex],
671 boost::algorithm::is_any_of("\t "),
672 boost::token_compress_on);
673
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530674 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530675 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530676 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
677 uint16_t value16 = 0;
678 if (tmp > std::numeric_limits<decltype(value16)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530679 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530680 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530681 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530682 value16 = static_cast<decltype(value16)>(tmp);
683 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 &&
684 value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530685 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530686 return true; // User account is locked out
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530687 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530688 return false; // User account is un-locked
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530689 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530690 catch (const std::exception &e)
691 {
692 log<level::ERR>("Exception for userLockedForFailedAttempt",
693 entry("WHAT=%s", e.what()));
694 throw;
695 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530696}
697
698bool UserMgr::userLockedForFailedAttempt(const std::string &userName,
699 const bool &value)
700{
701 // All user management lock has to be based on /etc/shadow
702 phosphor::user::shadow::Lock lock();
703 std::vector<std::string> output;
704 if (value == true)
705 {
706 return userLockedForFailedAttempt(userName);
707 }
708 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
709
710 std::vector<std::string> splitWords;
711 boost::algorithm::split(splitWords, output[t2OutputIndex],
712 boost::algorithm::is_any_of("\t "),
713 boost::token_compress_on);
714
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530715 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530716}
717
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530718UserSSHLists UserMgr::getUserAndSshGrpList()
719{
720 // All user management lock has to be based on /etc/shadow
721 phosphor::user::shadow::Lock lock();
722
723 std::vector<std::string> userList;
724 std::vector<std::string> sshUsersList;
725 struct passwd pw, *pwp = nullptr;
726 std::array<char, 1024> buffer{};
727
728 phosphor::user::File passwd(passwdFileName, "r");
729 if ((passwd)() == NULL)
730 {
731 log<level::ERR>("Error opening the passwd file");
732 elog<InternalFailure>();
733 }
734
735 while (true)
736 {
737 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
738 &pwp);
739 if ((r != 0) || (pwp == NULL))
740 {
741 // Any error, break the loop.
742 break;
743 }
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530744 // Add all users whose UID >= 1000 and < 65534
745 // and special UID 0.
746 if ((pwp->pw_uid == 0) ||
747 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530748 {
749 std::string userName(pwp->pw_name);
750 userList.emplace_back(userName);
751
752 // ssh doesn't have separate group. Check login shell entry to
753 // get all users list which are member of ssh group.
754 std::string loginShell(pwp->pw_shell);
755 if (loginShell == "/bin/sh")
756 {
757 sshUsersList.emplace_back(userName);
758 }
759 }
760 }
761 endpwent();
762 return std::make_pair(std::move(userList), std::move(sshUsersList));
763}
764
765size_t UserMgr::getIpmiUsersCount()
766{
767 std::vector<std::string> userList = getUsersInGroup("ipmi");
768 return userList.size();
769}
770
771bool UserMgr::isUserEnabled(const std::string &userName)
772{
773 // All user management lock has to be based on /etc/shadow
774 phosphor::user::shadow::Lock lock();
775 std::array<char, 4096> buffer{};
776 struct spwd spwd;
777 struct spwd *resultPtr = nullptr;
778 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
779 buffer.max_size(), &resultPtr);
780 if (!status && (&spwd == resultPtr))
781 {
782 if (resultPtr->sp_expire >= 0)
783 {
784 return false; // user locked out
785 }
786 return true;
787 }
788 return false; // assume user is disabled for any error.
789}
790
791std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName)
792{
793 std::vector<std::string> usersInGroup;
794 // Should be more than enough to get the pwd structure.
795 std::array<char, 4096> buffer{};
796 struct group grp;
797 struct group *resultPtr = nullptr;
798
799 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
800 buffer.max_size(), &resultPtr);
801
802 if (!status && (&grp == resultPtr))
803 {
804 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
805 {
806 usersInGroup.emplace_back(*(grp.gr_mem));
807 }
808 }
809 else
810 {
811 log<level::ERR>("Group not found",
812 entry("GROUP=%s", groupName.c_str()));
813 // Don't throw error, just return empty userList - fallback
814 }
815 return usersInGroup;
816}
817
818void UserMgr::initUserObjects(void)
819{
820 // All user management lock has to be based on /etc/shadow
821 phosphor::user::shadow::Lock lock();
822 std::vector<std::string> userNameList;
823 std::vector<std::string> sshGrpUsersList;
824 UserSSHLists userSSHLists = getUserAndSshGrpList();
825 userNameList = std::move(userSSHLists.first);
826 sshGrpUsersList = std::move(userSSHLists.second);
827
828 if (!userNameList.empty())
829 {
830 std::map<std::string, std::vector<std::string>> groupLists;
831 for (auto &grp : groupsMgr)
832 {
833 if (grp == grpSsh)
834 {
835 groupLists.emplace(grp, sshGrpUsersList);
836 }
837 else
838 {
839 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
840 groupLists.emplace(grp, grpUsersList);
841 }
842 }
843 for (auto &grp : privMgr)
844 {
845 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
846 groupLists.emplace(grp, grpUsersList);
847 }
848
849 for (auto &user : userNameList)
850 {
851 std::vector<std::string> userGroups;
852 std::string userPriv;
853 for (const auto &grp : groupLists)
854 {
855 std::vector<std::string> tempGrp = grp.second;
856 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
857 tempGrp.end())
858 {
859 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
860 privMgr.end())
861 {
862 userPriv = grp.first;
863 }
864 else
865 {
866 userGroups.emplace_back(grp.first);
867 }
868 }
869 }
870 // Add user objects to the Users path.
871 auto objPath = std::string(usersObjPath) + "/" + user;
872 std::sort(userGroups.begin(), userGroups.end());
873 usersList.emplace(user,
874 std::move(std::make_unique<phosphor::user::Users>(
875 bus, objPath.c_str(), userGroups, userPriv,
876 isUserEnabled(user), *this)));
877 }
878 }
879}
880
881UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) :
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530882 UserMgrIface(bus, path), AccountPolicyIface(bus, path), bus(bus), path(path)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530883{
884 UserMgrIface::allPrivileges(privMgr);
885 std::sort(groupsMgr.begin(), groupsMgr.end());
886 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530887 std::string valueStr;
888 auto value = minPasswdLength;
889 unsigned long tmp = 0;
890 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
891 success)
892 {
893 AccountPolicyIface::minPasswordLength(minPasswdLength);
894 }
895 else
896 {
897 try
898 {
899 tmp = std::stoul(valueStr, nullptr);
900 if (tmp > std::numeric_limits<decltype(value)>::max())
901 {
902 throw std::out_of_range("Out of range");
903 }
904 value = static_cast<decltype(value)>(tmp);
905 }
906 catch (const std::exception &e)
907 {
908 log<level::ERR>("Exception for MinPasswordLength",
909 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -0700910 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530911 }
912 AccountPolicyIface::minPasswordLength(value);
913 }
914 valueStr.clear();
915 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
916 success)
917 {
918 AccountPolicyIface::rememberOldPasswordTimes(0);
919 }
920 else
921 {
922 value = 0;
923 try
924 {
925 tmp = std::stoul(valueStr, nullptr);
926 if (tmp > std::numeric_limits<decltype(value)>::max())
927 {
928 throw std::out_of_range("Out of range");
929 }
930 value = static_cast<decltype(value)>(tmp);
931 }
932 catch (const std::exception &e)
933 {
934 log<level::ERR>("Exception for RememberOldPasswordTimes",
935 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -0700936 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530937 }
938 AccountPolicyIface::rememberOldPasswordTimes(value);
939 }
940 valueStr.clear();
941 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
942 {
943 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
944 }
945 else
946 {
947 uint16_t value16 = 0;
948 try
949 {
950 tmp = std::stoul(valueStr, nullptr);
951 if (tmp > std::numeric_limits<decltype(value16)>::max())
952 {
953 throw std::out_of_range("Out of range");
954 }
955 value16 = static_cast<decltype(value16)>(tmp);
956 }
957 catch (const std::exception &e)
958 {
959 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
960 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -0700961 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530962 }
963 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
964 }
965 valueStr.clear();
966 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
967 {
968 AccountPolicyIface::accountUnlockTimeout(0);
969 }
970 else
971 {
972 uint32_t value32 = 0;
973 try
974 {
975 tmp = std::stoul(valueStr, nullptr);
976 if (tmp > std::numeric_limits<decltype(value32)>::max())
977 {
978 throw std::out_of_range("Out of range");
979 }
980 value32 = static_cast<decltype(value32)>(tmp);
981 }
982 catch (const std::exception &e)
983 {
984 log<level::ERR>("Exception for AccountUnlockTimeout",
985 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -0700986 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530987 }
988 AccountPolicyIface::accountUnlockTimeout(value32);
989 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530990 initUserObjects();
991}
992
993} // namespace user
994} // namespace phosphor