blob: c5b068d21e0925576faff708eca5788ea3bc4769 [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
674 if (splitWords[t2UserIdx] == userName)
675 {
676 try
677 {
678 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
679 uint16_t value16 = 0;
680 if (tmp > std::numeric_limits<decltype(value16)>::max())
681 {
682 throw std::out_of_range("Out of range");
683 }
684 value16 = static_cast<decltype(value16)>(tmp);
685 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 &&
686 value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout())
687 {
688 return true; // User account is locked out
689 }
690 return false; // User account is un-locked
691 }
692 catch (const std::exception &e)
693 {
694 log<level::ERR>("Exception for userLockedForFailedAttempt",
695 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -0700696 throw;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530697 }
698 }
699 log<level::ERR>("Unable to get user account failed attempt",
700 entry("USER_NAME=%s", userName.c_str()));
701 elog<InternalFailure>();
702 return false;
703}
704
705bool UserMgr::userLockedForFailedAttempt(const std::string &userName,
706 const bool &value)
707{
708 // All user management lock has to be based on /etc/shadow
709 phosphor::user::shadow::Lock lock();
710 std::vector<std::string> output;
711 if (value == true)
712 {
713 return userLockedForFailedAttempt(userName);
714 }
715 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
716
717 std::vector<std::string> splitWords;
718 boost::algorithm::split(splitWords, output[t2OutputIndex],
719 boost::algorithm::is_any_of("\t "),
720 boost::token_compress_on);
721
722 if (splitWords[t2UserIdx] == userName)
723 {
724 return userLockedForFailedAttempt(userName);
725 }
726 log<level::ERR>("Unable to clear user account failed attempt");
727 elog<InternalFailure>();
728 return false;
729}
730
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530731UserSSHLists UserMgr::getUserAndSshGrpList()
732{
733 // All user management lock has to be based on /etc/shadow
734 phosphor::user::shadow::Lock lock();
735
736 std::vector<std::string> userList;
737 std::vector<std::string> sshUsersList;
738 struct passwd pw, *pwp = nullptr;
739 std::array<char, 1024> buffer{};
740
741 phosphor::user::File passwd(passwdFileName, "r");
742 if ((passwd)() == NULL)
743 {
744 log<level::ERR>("Error opening the passwd file");
745 elog<InternalFailure>();
746 }
747
748 while (true)
749 {
750 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
751 &pwp);
752 if ((r != 0) || (pwp == NULL))
753 {
754 // Any error, break the loop.
755 break;
756 }
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530757 // Add all users whose UID >= 1000 and < 65534
758 // and special UID 0.
759 if ((pwp->pw_uid == 0) ||
760 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530761 {
762 std::string userName(pwp->pw_name);
763 userList.emplace_back(userName);
764
765 // ssh doesn't have separate group. Check login shell entry to
766 // get all users list which are member of ssh group.
767 std::string loginShell(pwp->pw_shell);
768 if (loginShell == "/bin/sh")
769 {
770 sshUsersList.emplace_back(userName);
771 }
772 }
773 }
774 endpwent();
775 return std::make_pair(std::move(userList), std::move(sshUsersList));
776}
777
778size_t UserMgr::getIpmiUsersCount()
779{
780 std::vector<std::string> userList = getUsersInGroup("ipmi");
781 return userList.size();
782}
783
784bool UserMgr::isUserEnabled(const std::string &userName)
785{
786 // All user management lock has to be based on /etc/shadow
787 phosphor::user::shadow::Lock lock();
788 std::array<char, 4096> buffer{};
789 struct spwd spwd;
790 struct spwd *resultPtr = nullptr;
791 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
792 buffer.max_size(), &resultPtr);
793 if (!status && (&spwd == resultPtr))
794 {
795 if (resultPtr->sp_expire >= 0)
796 {
797 return false; // user locked out
798 }
799 return true;
800 }
801 return false; // assume user is disabled for any error.
802}
803
804std::vector<std::string> UserMgr::getUsersInGroup(const std::string &groupName)
805{
806 std::vector<std::string> usersInGroup;
807 // Should be more than enough to get the pwd structure.
808 std::array<char, 4096> buffer{};
809 struct group grp;
810 struct group *resultPtr = nullptr;
811
812 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
813 buffer.max_size(), &resultPtr);
814
815 if (!status && (&grp == resultPtr))
816 {
817 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
818 {
819 usersInGroup.emplace_back(*(grp.gr_mem));
820 }
821 }
822 else
823 {
824 log<level::ERR>("Group not found",
825 entry("GROUP=%s", groupName.c_str()));
826 // Don't throw error, just return empty userList - fallback
827 }
828 return usersInGroup;
829}
830
831void UserMgr::initUserObjects(void)
832{
833 // All user management lock has to be based on /etc/shadow
834 phosphor::user::shadow::Lock lock();
835 std::vector<std::string> userNameList;
836 std::vector<std::string> sshGrpUsersList;
837 UserSSHLists userSSHLists = getUserAndSshGrpList();
838 userNameList = std::move(userSSHLists.first);
839 sshGrpUsersList = std::move(userSSHLists.second);
840
841 if (!userNameList.empty())
842 {
843 std::map<std::string, std::vector<std::string>> groupLists;
844 for (auto &grp : groupsMgr)
845 {
846 if (grp == grpSsh)
847 {
848 groupLists.emplace(grp, sshGrpUsersList);
849 }
850 else
851 {
852 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
853 groupLists.emplace(grp, grpUsersList);
854 }
855 }
856 for (auto &grp : privMgr)
857 {
858 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
859 groupLists.emplace(grp, grpUsersList);
860 }
861
862 for (auto &user : userNameList)
863 {
864 std::vector<std::string> userGroups;
865 std::string userPriv;
866 for (const auto &grp : groupLists)
867 {
868 std::vector<std::string> tempGrp = grp.second;
869 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
870 tempGrp.end())
871 {
872 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
873 privMgr.end())
874 {
875 userPriv = grp.first;
876 }
877 else
878 {
879 userGroups.emplace_back(grp.first);
880 }
881 }
882 }
883 // Add user objects to the Users path.
884 auto objPath = std::string(usersObjPath) + "/" + user;
885 std::sort(userGroups.begin(), userGroups.end());
886 usersList.emplace(user,
887 std::move(std::make_unique<phosphor::user::Users>(
888 bus, objPath.c_str(), userGroups, userPriv,
889 isUserEnabled(user), *this)));
890 }
891 }
892}
893
894UserMgr::UserMgr(sdbusplus::bus::bus &bus, const char *path) :
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530895 UserMgrIface(bus, path), AccountPolicyIface(bus, path), bus(bus), path(path)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530896{
897 UserMgrIface::allPrivileges(privMgr);
898 std::sort(groupsMgr.begin(), groupsMgr.end());
899 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530900 std::string valueStr;
901 auto value = minPasswdLength;
902 unsigned long tmp = 0;
903 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
904 success)
905 {
906 AccountPolicyIface::minPasswordLength(minPasswdLength);
907 }
908 else
909 {
910 try
911 {
912 tmp = std::stoul(valueStr, nullptr);
913 if (tmp > std::numeric_limits<decltype(value)>::max())
914 {
915 throw std::out_of_range("Out of range");
916 }
917 value = static_cast<decltype(value)>(tmp);
918 }
919 catch (const std::exception &e)
920 {
921 log<level::ERR>("Exception for MinPasswordLength",
922 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -0700923 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530924 }
925 AccountPolicyIface::minPasswordLength(value);
926 }
927 valueStr.clear();
928 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
929 success)
930 {
931 AccountPolicyIface::rememberOldPasswordTimes(0);
932 }
933 else
934 {
935 value = 0;
936 try
937 {
938 tmp = std::stoul(valueStr, nullptr);
939 if (tmp > std::numeric_limits<decltype(value)>::max())
940 {
941 throw std::out_of_range("Out of range");
942 }
943 value = static_cast<decltype(value)>(tmp);
944 }
945 catch (const std::exception &e)
946 {
947 log<level::ERR>("Exception for RememberOldPasswordTimes",
948 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -0700949 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530950 }
951 AccountPolicyIface::rememberOldPasswordTimes(value);
952 }
953 valueStr.clear();
954 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
955 {
956 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
957 }
958 else
959 {
960 uint16_t value16 = 0;
961 try
962 {
963 tmp = std::stoul(valueStr, nullptr);
964 if (tmp > std::numeric_limits<decltype(value16)>::max())
965 {
966 throw std::out_of_range("Out of range");
967 }
968 value16 = static_cast<decltype(value16)>(tmp);
969 }
970 catch (const std::exception &e)
971 {
972 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
973 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -0700974 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530975 }
976 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
977 }
978 valueStr.clear();
979 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
980 {
981 AccountPolicyIface::accountUnlockTimeout(0);
982 }
983 else
984 {
985 uint32_t value32 = 0;
986 try
987 {
988 tmp = std::stoul(valueStr, nullptr);
989 if (tmp > std::numeric_limits<decltype(value32)>::max())
990 {
991 throw std::out_of_range("Out of range");
992 }
993 value32 = static_cast<decltype(value32)>(tmp);
994 }
995 catch (const std::exception &e)
996 {
997 log<level::ERR>("Exception for AccountUnlockTimeout",
998 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -0700999 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301000 }
1001 AccountPolicyIface::accountUnlockTimeout(value32);
1002 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301003 initUserObjects();
1004}
1005
1006} // namespace user
1007} // namespace phosphor