blob: f4a90ec3305942390e30ff2a47d6f74a3d5ec1ff [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
Patrick Williams9638afb2021-02-22 17:16:24 -060017#include "config.h"
18
19#include "user_mgr.hpp"
20
21#include "file.hpp"
22#include "shadowlock.hpp"
23#include "users.hpp"
24
25#include <grp.h>
26#include <pwd.h>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053027#include <shadow.h>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053028#include <sys/types.h>
29#include <sys/wait.h>
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -060030#include <time.h>
Patrick Williams9638afb2021-02-22 17:16:24 -060031#include <unistd.h>
32
33#include <boost/algorithm/string/split.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -060034#include <phosphor-logging/elog-errors.hpp>
35#include <phosphor-logging/elog.hpp>
36#include <phosphor-logging/log.hpp>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053037#include <xyz/openbmc_project/Common/error.hpp>
38#include <xyz/openbmc_project/User/Common/error.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -060039
40#include <algorithm>
Nan Zhouda401fe2022-10-25 00:07:18 +000041#include <array>
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +080042#include <ctime>
Patrick Williams9638afb2021-02-22 17:16:24 -060043#include <fstream>
44#include <numeric>
45#include <regex>
Nan Zhoue47c09d2022-10-25 00:06:41 +000046#include <span>
47#include <string>
48#include <string_view>
49#include <vector>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053050
51namespace phosphor
52{
53namespace user
54{
55
Patrick Williams9638afb2021-02-22 17:16:24 -060056static constexpr const char* passwdFileName = "/etc/passwd";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053057static constexpr size_t ipmiMaxUserNameLen = 16;
58static constexpr size_t systemMaxUserNameLen = 30;
Patrick Williams9638afb2021-02-22 17:16:24 -060059static constexpr const char* grpSsh = "ssh";
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +053060static constexpr int success = 0;
61static constexpr int failure = -1;
62
63// pam modules related
Patrick Williams9638afb2021-02-22 17:16:24 -060064static constexpr const char* pamTally2 = "pam_tally2.so";
65static constexpr const char* pamCrackLib = "pam_cracklib.so";
66static constexpr const char* pamPWHistory = "pam_pwhistory.so";
67static constexpr const char* minPasswdLenProp = "minlen";
68static constexpr const char* remOldPasswdCount = "remember";
69static constexpr const char* maxFailedAttempt = "deny";
70static constexpr const char* unlockTimeout = "unlock_time";
Nan Zhoue48085d2022-10-25 00:07:04 +000071static constexpr const char* defaultPamPasswdConfigFile =
72 "/etc/pam.d/common-password";
73static constexpr const char* defaultPamAuthConfigFile =
74 "/etc/pam.d/common-auth";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053075
Ratan Guptaaeaf9412019-02-11 04:41:52 -060076// Object Manager related
Patrick Williams9638afb2021-02-22 17:16:24 -060077static constexpr const char* ldapMgrObjBasePath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060078 "/xyz/openbmc_project/user/ldap";
79
80// Object Mapper related
Patrick Williams9638afb2021-02-22 17:16:24 -060081static constexpr const char* objMapperService =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060082 "xyz.openbmc_project.ObjectMapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060083static constexpr const char* objMapperPath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060084 "/xyz/openbmc_project/object_mapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060085static constexpr const char* objMapperInterface =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060086 "xyz.openbmc_project.ObjectMapper";
87
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053088using namespace phosphor::logging;
89using InsufficientPermission =
90 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
91using InternalFailure =
92 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
93using InvalidArgument =
94 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
95using UserNameExists =
96 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
97using UserNameDoesNotExist =
98 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
99using UserNameGroupFail =
100 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530101using NoResource =
102 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530103using Argument = xyz::openbmc_project::Common::InvalidArgument;
Nan Zhouda401fe2022-10-25 00:07:18 +0000104using GroupNameExists =
105 sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameExists;
106using GroupNameDoesNotExists =
107 sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameDoesNotExist;
108
109namespace
110{
111
112// The hardcoded groups in OpenBMC projects
113constexpr std::array<const char*, 4> predefinedGroups = {"web", "redfish",
114 "ipmi", "ssh"};
115
116// These prefixes are for Dynamic Redfish authorization. See
117// https://github.com/openbmc/docs/blob/master/designs/redfish-authorization.md
118
119// Base role and base privileges are added by Redfish implementation (e.g.,
120// BMCWeb) at compile time
121constexpr std::array<const char*, 4> allowedGroupPrefix = {
122 "openbmc_rfr_", // OpenBMC Redfish Base Role
123 "openbmc_rfp_", // OpenBMC Redfish Base Privileges
124 "openbmc_orfr_", // OpenBMC Redfish OEM Role
125 "openbmc_orfp_", // OpenBMC Redfish OEM Privileges
126};
127
128void checkAndThrowsForGroupChangeAllowed(const std::string& groupName)
129{
130 bool allowed = false;
131 for (std::string_view prefix : allowedGroupPrefix)
132 {
133 if (groupName.starts_with(prefix))
134 {
135 allowed = true;
136 break;
137 }
138 }
139 if (!allowed)
140 {
141 log<level::ERR>("Invalid group name; not in the allowed list",
142 entry("GroupName=%s", groupName.c_str()));
143 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
144 Argument::ARGUMENT_VALUE(groupName.c_str()));
145 }
146}
147
148} // namespace
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530149
Nan Zhoue47c09d2022-10-25 00:06:41 +0000150std::string getCSVFromVector(std::span<const std::string> vec)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530151{
Nan Zhoue47c09d2022-10-25 00:06:41 +0000152 if (vec.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530153 {
Nan Zhoue47c09d2022-10-25 00:06:41 +0000154 return "";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530155 }
Nan Zhoue47c09d2022-10-25 00:06:41 +0000156 return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
157 [](std::string&& val, std::string_view element) {
158 val += ',';
159 val += element;
160 return val;
161 });
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530162}
163
Nan Zhou332fb9d2022-10-25 00:07:03 +0000164bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530165{
166 std::string::size_type delStrPos = csvStr.find(delStr);
167 if (delStrPos != std::string::npos)
168 {
169 // need to also delete the comma char
170 if (delStrPos == 0)
171 {
172 csvStr.erase(delStrPos, delStr.size() + 1);
173 }
174 else
175 {
176 csvStr.erase(delStrPos - 1, delStr.size() + 1);
177 }
178 return true;
179 }
180 return false;
181}
182
Patrick Williams9638afb2021-02-22 17:16:24 -0600183bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530184{
185 if (userName.empty())
186 {
187 log<level::ERR>("User name is empty");
188 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
189 Argument::ARGUMENT_VALUE("Null"));
190 }
191 if (usersList.find(userName) == usersList.end())
192 {
193 return false;
194 }
195 return true;
196}
197
Patrick Williams9638afb2021-02-22 17:16:24 -0600198void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530199{
Nan Zhou8a11d992022-10-25 00:07:06 +0000200 if (!isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530201 {
202 log<level::ERR>("User does not exist",
203 entry("USER_NAME=%s", userName.c_str()));
204 elog<UserNameDoesNotExist>();
205 }
206}
207
Nan Zhouda401fe2022-10-25 00:07:18 +0000208void UserMgr::checkAndThrowForDisallowedGroupCreation(
209 const std::string& groupName)
210{
211 if (groupName.size() > maxSystemGroupNameLength ||
212 !std::regex_match(groupName.c_str(),
213 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
214 {
215 log<level::ERR>("Invalid group name",
216 entry("GroupName=%s", groupName.c_str()));
217 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
218 Argument::ARGUMENT_VALUE(groupName.c_str()));
219 }
220 checkAndThrowsForGroupChangeAllowed(groupName);
221}
222
Patrick Williams9638afb2021-02-22 17:16:24 -0600223void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530224{
Nan Zhou8a11d992022-10-25 00:07:06 +0000225 if (isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530226 {
227 log<level::ERR>("User already exists",
228 entry("USER_NAME=%s", userName.c_str()));
229 elog<UserNameExists>();
230 }
231}
232
233void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600234 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530235{
236 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
237 groupNames.end())
238 {
239 if (userName.length() > ipmiMaxUserNameLen)
240 {
241 log<level::ERR>("IPMI user name length limitation",
242 entry("SIZE=%d", userName.length()));
243 elog<UserNameGroupFail>(
244 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
245 "IPMI length"));
246 }
247 }
248 if (userName.length() > systemMaxUserNameLen)
249 {
250 log<level::ERR>("User name length limitation",
251 entry("SIZE=%d", userName.length()));
252 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
253 Argument::ARGUMENT_VALUE("Invalid length"));
254 }
255 if (!std::regex_match(userName.c_str(),
256 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
257 {
258 log<level::ERR>("Invalid user name",
259 entry("USER_NAME=%s", userName.c_str()));
260 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
261 Argument::ARGUMENT_VALUE("Invalid data"));
262 }
263}
264
265void UserMgr::throwForMaxGrpUserCount(
Patrick Williams9638afb2021-02-22 17:16:24 -0600266 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530267{
268 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
269 groupNames.end())
270 {
271 if (getIpmiUsersCount() >= ipmiMaxUsers)
272 {
273 log<level::ERR>("IPMI user limit reached");
274 elog<NoResource>(
275 xyz::openbmc_project::User::Common::NoResource::REASON(
276 "ipmi user count reached"));
277 }
278 }
279 else
280 {
281 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
282 (maxSystemUsers - ipmiMaxUsers))
283 {
284 log<level::ERR>("Non-ipmi User limit reached");
285 elog<NoResource>(
286 xyz::openbmc_project::User::Common::NoResource::REASON(
287 "Non-ipmi user count reached"));
288 }
289 }
290 return;
291}
292
Patrick Williams9638afb2021-02-22 17:16:24 -0600293void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530294{
295 if (!priv.empty() &&
296 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
297 {
298 log<level::ERR>("Invalid privilege");
299 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
300 Argument::ARGUMENT_VALUE(priv.c_str()));
301 }
302}
303
Patrick Williams9638afb2021-02-22 17:16:24 -0600304void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530305{
Patrick Williams9638afb2021-02-22 17:16:24 -0600306 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530307 {
308 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
309 groupsMgr.end())
310 {
311 log<level::ERR>("Invalid Group Name listed");
312 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
313 Argument::ARGUMENT_VALUE(group.c_str()));
314 }
315 }
316}
317
Nan Zhouda401fe2022-10-25 00:07:18 +0000318std::vector<std::string> UserMgr::readAllGroupsOnSystem()
319{
320 std::vector<std::string> allGroups = {predefinedGroups.begin(),
321 predefinedGroups.end()};
322 // rewinds to the beginning of the group database
323 setgrent();
324 struct group* gr = getgrent();
325 while (gr != nullptr)
326 {
327 std::string group(gr->gr_name);
328 for (std::string_view prefix : allowedGroupPrefix)
329 {
330 if (group.starts_with(prefix))
331 {
332 allGroups.push_back(gr->gr_name);
333 }
334 }
335 gr = getgrent();
336 }
337 // close the group database
338 endgrent();
339 return allGroups;
340}
341
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530342void UserMgr::createUser(std::string userName,
343 std::vector<std::string> groupNames, std::string priv,
344 bool enabled)
345{
346 throwForInvalidPrivilege(priv);
347 throwForInvalidGroups(groupNames);
348 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500349 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530350 throwForUserExists(userName);
351 throwForUserNameConstraints(userName, groupNames);
352 throwForMaxGrpUserCount(groupNames);
353
354 std::string groups = getCSVFromVector(groupNames);
355 bool sshRequested = removeStringFromCSV(groups, grpSsh);
356
357 // treat privilege as a group - This is to avoid using different file to
358 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530359 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530360 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530361 if (groups.size() != 0)
362 {
363 groups += ",";
364 }
365 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530366 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530367 try
368 {
Nan Zhou49c81362022-10-25 00:07:08 +0000369 executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530370 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600371 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530372 {
373 log<level::ERR>("Unable to create new user");
374 elog<InternalFailure>();
375 }
376
377 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530378 sdbusplus::message::object_path tempObjPath(usersObjPath);
379 tempObjPath /= userName;
380 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530381 std::sort(groupNames.begin(), groupNames.end());
382 usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +0000383 userName, std::make_unique<phosphor::user::Users>(
384 bus, userObj.c_str(), groupNames, priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530385
386 log<level::INFO>("User created successfully",
387 entry("USER_NAME=%s", userName.c_str()));
388 return;
389}
390
391void UserMgr::deleteUser(std::string userName)
392{
393 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500394 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530395 throwForUserDoesNotExist(userName);
396 try
397 {
Nan Zhou49c81362022-10-25 00:07:08 +0000398 executeUserDelete(userName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530399 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600400 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530401 {
402 log<level::ERR>("User delete failed",
403 entry("USER_NAME=%s", userName.c_str()));
404 elog<InternalFailure>();
405 }
406
407 usersList.erase(userName);
408
409 log<level::INFO>("User deleted successfully",
410 entry("USER_NAME=%s", userName.c_str()));
411 return;
412}
413
Nan Zhouda401fe2022-10-25 00:07:18 +0000414void UserMgr::checkDeleteGroupConstraints(const std::string& groupName)
415{
416 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) ==
417 groupsMgr.end())
418 {
419 log<level::ERR>("Group already exists",
420 entry("GROUP_NAME=%s", groupName.c_str()));
421 elog<GroupNameDoesNotExists>();
422 }
423 checkAndThrowsForGroupChangeAllowed(groupName);
424}
425
426void UserMgr::deleteGroup(std::string groupName)
427{
428 checkDeleteGroupConstraints(groupName);
429 try
430 {
431 executeGroupDeletion(groupName.c_str());
432 }
433 catch (const InternalFailure& e)
434 {
435 log<level::ERR>("Group delete failed",
436 entry("GROUP_NAME=%s", groupName.c_str()));
437 elog<InternalFailure>();
438 }
439
440 groupsMgr.erase(std::find(groupsMgr.begin(), groupsMgr.end(), groupName));
441 UserMgrIface::allGroups(groupsMgr);
442 log<level::INFO>("Group deleted successfully",
443 entry("GROUP_NAME=%s", groupName.c_str()));
444}
445
446void UserMgr::checkCreateGroupConstraints(const std::string& groupName)
447{
448 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) !=
449 groupsMgr.end())
450 {
451 log<level::ERR>("Group already exists",
452 entry("GROUP_NAME=%s", groupName.c_str()));
453 elog<GroupNameExists>();
454 }
455 checkAndThrowForDisallowedGroupCreation(groupName);
456 if (groupsMgr.size() >= maxSystemGroupCount)
457 {
458 log<level::ERR>("Group limit reached");
459 elog<NoResource>(xyz::openbmc_project::User::Common::NoResource::REASON(
460 "Group limit reached"));
461 }
462}
463
464void UserMgr::createGroup(std::string groupName)
465{
466 checkCreateGroupConstraints(groupName);
467 try
468 {
469 executeGroupCreation(groupName.c_str());
470 }
471 catch (const InternalFailure& e)
472 {
473 log<level::ERR>("Group create failed",
474 entry("GROUP_NAME=%s", groupName.c_str()));
475 elog<InternalFailure>();
476 }
477 groupsMgr.push_back(groupName);
478 UserMgrIface::allGroups(groupsMgr);
479}
480
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530481void UserMgr::renameUser(std::string userName, std::string newUserName)
482{
483 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500484 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530485 throwForUserDoesNotExist(userName);
486 throwForUserExists(newUserName);
487 throwForUserNameConstraints(newUserName,
488 usersList[userName].get()->userGroups());
489 try
490 {
Nan Zhouf25443e2022-10-25 00:07:11 +0000491 executeUserRename(userName.c_str(), newUserName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530492 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600493 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530494 {
495 log<level::ERR>("User rename failed",
496 entry("USER_NAME=%s", userName.c_str()));
497 elog<InternalFailure>();
498 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600499 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530500 std::string priv = user.get()->userPrivilege();
501 std::vector<std::string> groupNames = user.get()->userGroups();
502 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530503 sdbusplus::message::object_path tempObjPath(usersObjPath);
504 tempObjPath /= newUserName;
505 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530506 // Special group 'ipmi' needs a way to identify user renamed, in order to
507 // update encrypted password. It can't rely only on InterfacesRemoved &
508 // InterfacesAdded. So first send out userRenamed signal.
509 this->userRenamed(userName, newUserName);
510 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000511 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
512 bus, newUserObj.c_str(), groupNames,
513 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530514 return;
515}
516
Patrick Williams9638afb2021-02-22 17:16:24 -0600517void UserMgr::updateGroupsAndPriv(const std::string& userName,
Nan Zhoufef63032022-10-25 00:07:12 +0000518 std::vector<std::string> groupNames,
Patrick Williams9638afb2021-02-22 17:16:24 -0600519 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530520{
521 throwForInvalidPrivilege(priv);
522 throwForInvalidGroups(groupNames);
523 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500524 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530525 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600526 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530527 usersList[userName].get()->userGroups();
528 std::vector<std::string> groupDiff;
529 // Note: already dealing with sorted group lists.
530 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
531 groupNames.begin(), groupNames.end(),
532 std::back_inserter(groupDiff));
533 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
534 groupDiff.end())
535 {
536 throwForUserNameConstraints(userName, groupNames);
537 throwForMaxGrpUserCount(groupNames);
538 }
539
540 std::string groups = getCSVFromVector(groupNames);
541 bool sshRequested = removeStringFromCSV(groups, grpSsh);
542
543 // treat privilege as a group - This is to avoid using different file to
544 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530545 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530546 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530547 if (groups.size() != 0)
548 {
549 groups += ",";
550 }
551 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530552 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530553 try
554 {
Nan Zhoufef63032022-10-25 00:07:12 +0000555 executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530556 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600557 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530558 {
559 log<level::ERR>("Unable to modify user privilege / groups");
560 elog<InternalFailure>();
561 }
562
563 log<level::INFO>("User groups / privilege updated successfully",
564 entry("USER_NAME=%s", userName.c_str()));
Nan Zhoufef63032022-10-25 00:07:12 +0000565 std::sort(groupNames.begin(), groupNames.end());
566 usersList[userName]->setUserGroups(groupNames);
567 usersList[userName]->setUserPrivilege(priv);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530568 return;
569}
570
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530571uint8_t UserMgr::minPasswordLength(uint8_t value)
572{
573 if (value == AccountPolicyIface::minPasswordLength())
574 {
575 return value;
576 }
577 if (value < minPasswdLength)
578 {
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000579 log<level::ERR>(("Attempting to set minPasswordLength to less than " +
580 std::to_string(minPasswdLength))
581 .c_str(),
582 entry("SIZE=%d", value));
583 elog<InvalidArgument>(
584 Argument::ARGUMENT_NAME("minPasswordLength"),
585 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530586 }
587 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
588 std::to_string(value)) != success)
589 {
590 log<level::ERR>("Unable to set minPasswordLength");
591 elog<InternalFailure>();
592 }
593 return AccountPolicyIface::minPasswordLength(value);
594}
595
596uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
597{
598 if (value == AccountPolicyIface::rememberOldPasswordTimes())
599 {
600 return value;
601 }
602 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
603 std::to_string(value)) != success)
604 {
605 log<level::ERR>("Unable to set rememberOldPasswordTimes");
606 elog<InternalFailure>();
607 }
608 return AccountPolicyIface::rememberOldPasswordTimes(value);
609}
610
611uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
612{
613 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
614 {
615 return value;
616 }
617 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
618 std::to_string(value)) != success)
619 {
620 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
621 elog<InternalFailure>();
622 }
623 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
624}
625
626uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
627{
628 if (value == AccountPolicyIface::accountUnlockTimeout())
629 {
630 return value;
631 }
632 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
633 success)
634 {
635 log<level::ERR>("Unable to set accountUnlockTimeout");
636 elog<InternalFailure>();
637 }
638 return AccountPolicyIface::accountUnlockTimeout(value);
639}
640
Patrick Williams9638afb2021-02-22 17:16:24 -0600641int UserMgr::getPamModuleArgValue(const std::string& moduleName,
642 const std::string& argName,
643 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530644{
645 std::string fileName;
646 if (moduleName == pamTally2)
647 {
648 fileName = pamAuthConfigFile;
649 }
650 else
651 {
652 fileName = pamPasswdConfigFile;
653 }
654 std::ifstream fileToRead(fileName, std::ios::in);
655 if (!fileToRead.is_open())
656 {
657 log<level::ERR>("Failed to open pam configuration file",
658 entry("FILE_NAME=%s", fileName.c_str()));
659 return failure;
660 }
661 std::string line;
662 auto argSearch = argName + "=";
663 size_t startPos = 0;
664 size_t endPos = 0;
665 while (getline(fileToRead, line))
666 {
667 // skip comments section starting with #
668 if ((startPos = line.find('#')) != std::string::npos)
669 {
670 if (startPos == 0)
671 {
672 continue;
673 }
674 // skip comments after meaningful section and process those
675 line = line.substr(0, startPos);
676 }
677 if (line.find(moduleName) != std::string::npos)
678 {
679 if ((startPos = line.find(argSearch)) != std::string::npos)
680 {
681 if ((endPos = line.find(' ', startPos)) == std::string::npos)
682 {
683 endPos = line.size();
684 }
685 startPos += argSearch.size();
686 argValue = line.substr(startPos, endPos - startPos);
687 return success;
688 }
689 }
690 }
691 return failure;
692}
693
Patrick Williams9638afb2021-02-22 17:16:24 -0600694int UserMgr::setPamModuleArgValue(const std::string& moduleName,
695 const std::string& argName,
696 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530697{
698 std::string fileName;
699 if (moduleName == pamTally2)
700 {
701 fileName = pamAuthConfigFile;
702 }
703 else
704 {
705 fileName = pamPasswdConfigFile;
706 }
707 std::string tmpFileName = fileName + "_tmp";
708 std::ifstream fileToRead(fileName, std::ios::in);
709 std::ofstream fileToWrite(tmpFileName, std::ios::out);
710 if (!fileToRead.is_open() || !fileToWrite.is_open())
711 {
712 log<level::ERR>("Failed to open pam configuration /tmp file",
713 entry("FILE_NAME=%s", fileName.c_str()));
714 return failure;
715 }
716 std::string line;
717 auto argSearch = argName + "=";
718 size_t startPos = 0;
719 size_t endPos = 0;
720 bool found = false;
721 while (getline(fileToRead, line))
722 {
723 // skip comments section starting with #
724 if ((startPos = line.find('#')) != std::string::npos)
725 {
726 if (startPos == 0)
727 {
728 fileToWrite << line << std::endl;
729 continue;
730 }
731 // skip comments after meaningful section and process those
732 line = line.substr(0, startPos);
733 }
734 if (line.find(moduleName) != std::string::npos)
735 {
736 if ((startPos = line.find(argSearch)) != std::string::npos)
737 {
738 if ((endPos = line.find(' ', startPos)) == std::string::npos)
739 {
740 endPos = line.size();
741 }
742 startPos += argSearch.size();
743 fileToWrite << line.substr(0, startPos) << argValue
744 << line.substr(endPos, line.size() - endPos)
745 << std::endl;
746 found = true;
747 continue;
748 }
749 }
750 fileToWrite << line << std::endl;
751 }
752 fileToWrite.close();
753 fileToRead.close();
754 if (found)
755 {
756 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
757 {
758 return success;
759 }
760 }
761 return failure;
762}
763
Patrick Williams9638afb2021-02-22 17:16:24 -0600764void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530765{
766 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500767 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530768 throwForUserDoesNotExist(userName);
769 try
770 {
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000771 executeUserModifyUserEnable(userName.c_str(), enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530772 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600773 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530774 {
775 log<level::ERR>("Unable to modify user enabled state");
776 elog<InternalFailure>();
777 }
778
779 log<level::INFO>("User enabled/disabled state updated successfully",
780 entry("USER_NAME=%s", userName.c_str()),
781 entry("ENABLED=%d", enabled));
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000782 usersList[userName]->setUserEnabled(enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530783 return;
784}
785
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530786/**
787 * pam_tally2 app will provide the user failure count and failure status
788 * in second line of output with words position [0] - user name,
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800789 * [1] - failure count, [2] - latest failure date, [3] - latest failure time
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530790 * [4] - failure app
791 **/
792
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530793static constexpr size_t t2FailCntIdx = 1;
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800794static constexpr size_t t2FailDateIdx = 2;
795static constexpr size_t t2FailTimeIdx = 3;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530796static constexpr size_t t2OutputIndex = 1;
797
Patrick Williams9638afb2021-02-22 17:16:24 -0600798bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530799{
800 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500801 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800802 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
803 {
804 return false;
805 }
806
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530807 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800808 try
809 {
Nan Zhoua2953032022-11-11 21:50:32 +0000810 output = getFailedAttempt(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800811 }
812 catch (const InternalFailure& e)
813 {
814 log<level::ERR>("Unable to read login failure counter");
815 elog<InternalFailure>();
816 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530817
818 std::vector<std::string> splitWords;
819 boost::algorithm::split(splitWords, output[t2OutputIndex],
820 boost::algorithm::is_any_of("\t "),
821 boost::token_compress_on);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800822 uint16_t failAttempts = 0;
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530823 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530824 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530825 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800826 if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530827 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530828 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530829 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800830 failAttempts = static_cast<decltype(failAttempts)>(tmp);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530831 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600832 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530833 {
834 log<level::ERR>("Exception for userLockedForFailedAttempt",
835 entry("WHAT=%s", e.what()));
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800836 elog<InternalFailure>();
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530837 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800838
839 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
840 {
841 return false;
842 }
843
844 // When failedAttempts is not 0, Latest failure date/time should be
845 // available
846 if (splitWords.size() < 4)
847 {
848 log<level::ERR>("Unable to read latest failure date/time");
849 elog<InternalFailure>();
850 }
851
852 const std::string failDateTime =
853 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
854
855 // NOTE: Cannot use std::get_time() here as the implementation of %y in
856 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
857 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
858 std::tm tmStruct = {};
859 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
860 {
861 log<level::ERR>("Failed to parse latest failure date/time");
862 elog<InternalFailure>();
863 }
864
865 time_t failTimestamp = std::mktime(&tmStruct);
866 if (failTimestamp +
867 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
868 std::time(NULL))
869 {
870 return false;
871 }
872
873 return true;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530874}
875
Patrick Williams9638afb2021-02-22 17:16:24 -0600876bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
877 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530878{
879 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500880 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530881 if (value == true)
882 {
883 return userLockedForFailedAttempt(userName);
884 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530885
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800886 try
887 {
888 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
889 }
890 catch (const InternalFailure& e)
891 {
892 log<level::ERR>("Unable to reset login failure counter");
893 elog<InternalFailure>();
894 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530895
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530896 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530897}
898
Patrick Williams9638afb2021-02-22 17:16:24 -0600899bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600900{
901 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500902 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600903
904 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600905 {};
906 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600907 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
908 if (buflen < -1)
909 {
910 // Use a default size if there is no hard limit suggested by sysconf()
911 buflen = 1024;
912 }
913 std::vector<char> buffer(buflen);
914 auto status =
915 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
916 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
917 // If no matching password record was found, these functions return 0
918 // and store NULL in *spwdPtr
919 if ((status == 0) && (&spwd == spwdPtr))
920 {
921 // Determine password validity per "chage" docs, where:
922 // spwd.sp_lstchg == 0 means password is expired, and
923 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000924 constexpr long secondsPerDay = 60 * 60 * 24;
925 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600926 if ((spwd.sp_lstchg == 0) ||
927 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
928 {
929 return true;
930 }
931 }
932 else
933 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000934 // User entry is missing in /etc/shadow, indicating no SHA password.
935 // Treat this as new user without password entry in /etc/shadow
936 // TODO: Add property to indicate user password was not set yet
937 // https://github.com/openbmc/phosphor-user-manager/issues/8
938 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600939 }
940
941 return false;
942}
943
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530944UserSSHLists UserMgr::getUserAndSshGrpList()
945{
946 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500947 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530948
949 std::vector<std::string> userList;
950 std::vector<std::string> sshUsersList;
951 struct passwd pw, *pwp = nullptr;
952 std::array<char, 1024> buffer{};
953
954 phosphor::user::File passwd(passwdFileName, "r");
955 if ((passwd)() == NULL)
956 {
957 log<level::ERR>("Error opening the passwd file");
958 elog<InternalFailure>();
959 }
960
961 while (true)
962 {
963 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
964 &pwp);
965 if ((r != 0) || (pwp == NULL))
966 {
967 // Any error, break the loop.
968 break;
969 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530970#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530971 // Add all users whose UID >= 1000 and < 65534
972 // and special UID 0.
973 if ((pwp->pw_uid == 0) ||
974 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530975#else
976 // Add all users whose UID >=1000 and < 65534
977 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
978#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530979 {
980 std::string userName(pwp->pw_name);
981 userList.emplace_back(userName);
982
983 // ssh doesn't have separate group. Check login shell entry to
984 // get all users list which are member of ssh group.
985 std::string loginShell(pwp->pw_shell);
986 if (loginShell == "/bin/sh")
987 {
988 sshUsersList.emplace_back(userName);
989 }
990 }
991 }
992 endpwent();
993 return std::make_pair(std::move(userList), std::move(sshUsersList));
994}
995
996size_t UserMgr::getIpmiUsersCount()
997{
998 std::vector<std::string> userList = getUsersInGroup("ipmi");
999 return userList.size();
1000}
1001
Nan Zhou49c81362022-10-25 00:07:08 +00001002size_t UserMgr::getNonIpmiUsersCount()
1003{
1004 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
1005 return usersList.size() - ipmiUsers.size();
1006}
1007
Patrick Williams9638afb2021-02-22 17:16:24 -06001008bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301009{
1010 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001011 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301012 std::array<char, 4096> buffer{};
1013 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -06001014 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301015 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
1016 buffer.max_size(), &resultPtr);
1017 if (!status && (&spwd == resultPtr))
1018 {
1019 if (resultPtr->sp_expire >= 0)
1020 {
1021 return false; // user locked out
1022 }
1023 return true;
1024 }
1025 return false; // assume user is disabled for any error.
1026}
1027
Patrick Williams9638afb2021-02-22 17:16:24 -06001028std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301029{
1030 std::vector<std::string> usersInGroup;
1031 // Should be more than enough to get the pwd structure.
1032 std::array<char, 4096> buffer{};
1033 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -06001034 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301035
1036 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1037 buffer.max_size(), &resultPtr);
1038
1039 if (!status && (&grp == resultPtr))
1040 {
1041 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
1042 {
1043 usersInGroup.emplace_back(*(grp.gr_mem));
1044 }
1045 }
1046 else
1047 {
1048 log<level::ERR>("Group not found",
1049 entry("GROUP=%s", groupName.c_str()));
1050 // Don't throw error, just return empty userList - fallback
1051 }
1052 return usersInGroup;
1053}
1054
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001055DbusUserObj UserMgr::getPrivilegeMapperObject(void)
1056{
1057 DbusUserObj objects;
1058 try
1059 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001060 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
1061 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001062
1063 auto ldapMgmtService =
1064 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001065 auto method = bus.new_method_call(
1066 ldapMgmtService.c_str(), ldapMgrObjBasePath,
1067 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1068
1069 auto reply = bus.call(method);
1070 reply.read(objects);
1071 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001072 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001073 {
1074 log<level::ERR>("Unable to get the User Service",
1075 entry("WHAT=%s", e.what()));
1076 throw;
1077 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001078 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001079 {
1080 log<level::ERR>(
1081 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
1082 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
1083 throw;
1084 }
1085 return objects;
1086}
1087
Patrick Williams9638afb2021-02-22 17:16:24 -06001088std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001089{
1090 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1091 objMapperInterface, "GetObject");
1092
1093 mapperCall.append(std::move(path));
1094 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1095
1096 auto mapperResponseMsg = bus.call(mapperCall);
1097
1098 if (mapperResponseMsg.is_method_error())
1099 {
1100 log<level::ERR>("Error in mapper call");
1101 elog<InternalFailure>();
1102 }
1103
1104 std::map<std::string, std::vector<std::string>> mapperResponse;
1105 mapperResponseMsg.read(mapperResponse);
1106
1107 if (mapperResponse.begin() == mapperResponse.end())
1108 {
1109 log<level::ERR>("Invalid response from mapper");
1110 elog<InternalFailure>();
1111 }
1112
1113 return mapperResponse.begin()->first;
1114}
1115
Alexander Filippov75626582022-02-09 18:42:37 +03001116gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
1117{
1118 static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1119 if (buflen <= 0)
1120 {
1121 // Use a default size if there is no hard limit suggested by sysconf()
1122 buflen = 1024;
1123 }
1124
1125 struct passwd pwd;
1126 struct passwd* pwdPtr = nullptr;
1127 std::vector<char> buffer(buflen);
1128
1129 auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(),
1130 buffer.size(), &pwdPtr);
1131 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
1132 // If no matching password record was found, these functions return 0
1133 // and store NULL in *pwdPtr
1134 if (!status && (&pwd == pwdPtr))
1135 {
1136 return pwd.pw_gid;
1137 }
1138
1139 log<level::ERR>("User noes not exist",
1140 entry("USER_NAME=%s", userName.c_str()));
1141 elog<UserNameDoesNotExist>();
1142}
1143
1144bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
1145 const std::string& groupName) const
1146{
1147 static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1148 if (buflen <= 0)
1149 {
1150 // Use a default size if there is no hard limit suggested by sysconf()
1151 buflen = 1024;
1152 }
1153
1154 struct group grp;
1155 struct group* grpPtr = nullptr;
1156 std::vector<char> buffer(buflen);
1157
1158 auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1159 buffer.size(), &grpPtr);
1160
1161 // Groups with a lot of members may require a buffer of bigger size than
1162 // suggested by _SC_GETGR_R_SIZE_MAX.
1163 // 32K should be enough for about 2K members.
1164 constexpr auto maxBufferLength = 32 * 1024;
1165 while (status == ERANGE && buflen < maxBufferLength)
1166 {
1167 buflen *= 2;
1168 buffer.resize(buflen);
1169
1170 log<level::DEBUG>("Increase buffer for getgrnam_r()",
1171 entry("BUFFER_LENGTH=%zu", buflen));
1172
1173 status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1174 buffer.size(), &grpPtr);
1175 }
1176
1177 // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
1178 // If no matching group record was found, these functions return 0
1179 // and store NULL in *grpPtr
1180 if (!status && (&grp == grpPtr))
1181 {
1182 if (primaryGid == grp.gr_gid)
1183 {
1184 return true;
1185 }
1186
1187 for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
1188 {
1189 if (userName == grp.gr_mem[i])
1190 {
1191 return true;
1192 }
1193 }
1194 }
1195 else if (status == ERANGE)
1196 {
1197 log<level::ERR>("Group info requires too much memory",
1198 entry("GROUP_NAME=%s", groupName.c_str()));
1199 }
1200 else
1201 {
1202 log<level::ERR>("Group does not exist",
1203 entry("GROUP_NAME=%s", groupName.c_str()));
1204 }
1205
1206 return false;
1207}
1208
Nan Zhouda401fe2022-10-25 00:07:18 +00001209void UserMgr::executeGroupCreation(const char* groupName)
1210{
1211 executeCmd("/usr/sbin/groupadd", groupName);
1212}
1213
1214void UserMgr::executeGroupDeletion(const char* groupName)
1215{
1216 executeCmd("/usr/sbin/groupdel", groupName);
1217}
1218
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001219UserInfoMap UserMgr::getUserInfo(std::string userName)
1220{
1221 UserInfoMap userInfo;
1222 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001223 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001224 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001225 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001226 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1227 userInfo.emplace("UserGroups", user.get()->userGroups());
1228 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1229 userInfo.emplace("UserLockedForFailedAttempt",
1230 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001231 userInfo.emplace("UserPasswordExpired",
1232 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001233 userInfo.emplace("RemoteUser", false);
1234 }
1235 else
1236 {
Alexander Filippov75626582022-02-09 18:42:37 +03001237 auto primaryGid = getPrimaryGroup(userName);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001238
1239 DbusUserObj objects = getPrivilegeMapperObject();
1240
Ravi Teja5fe724a2019-05-07 05:14:42 -05001241 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001242 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001243
1244 try
1245 {
Alexander Filippov75626582022-02-09 18:42:37 +03001246 for (const auto& [path, interfaces] : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001247 {
Alexander Filippov75626582022-02-09 18:42:37 +03001248 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
1249 if (it != interfaces.end())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001250 {
Alexander Filippov75626582022-02-09 18:42:37 +03001251 auto propIt = it->second.find("Enabled");
1252 if (propIt != it->second.end() &&
1253 std::get<bool>(propIt->second))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001254 {
Alexander Filippov75626582022-02-09 18:42:37 +03001255 ldapConfigPath = path.str + '/';
1256 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001257 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001258 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001259 }
1260
1261 if (ldapConfigPath.empty())
1262 {
1263 return userInfo;
1264 }
1265
Alexander Filippov75626582022-02-09 18:42:37 +03001266 for (const auto& [path, interfaces] : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001267 {
Alexander Filippov75626582022-02-09 18:42:37 +03001268 if (!path.str.starts_with(ldapConfigPath))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001269 {
Alexander Filippov75626582022-02-09 18:42:37 +03001270 continue;
1271 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001272
Alexander Filippov75626582022-02-09 18:42:37 +03001273 auto it = interfaces.find(
1274 "xyz.openbmc_project.User.PrivilegeMapperEntry");
1275 if (it != interfaces.end())
1276 {
1277 std::string privilege;
1278 std::string groupName;
1279
1280 for (const auto& [propName, propValue] : it->second)
1281 {
1282 if (propName == "GroupName")
Ravi Teja5fe724a2019-05-07 05:14:42 -05001283 {
Alexander Filippov75626582022-02-09 18:42:37 +03001284 groupName = std::get<std::string>(propValue);
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001285 }
Alexander Filippov75626582022-02-09 18:42:37 +03001286 else if (propName == "Privilege")
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001287 {
Alexander Filippov75626582022-02-09 18:42:37 +03001288 privilege = std::get<std::string>(propValue);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001289 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001290 }
Alexander Filippov75626582022-02-09 18:42:37 +03001291
1292 if (!groupName.empty() && !privilege.empty() &&
1293 isGroupMember(userName, primaryGid, groupName))
1294 {
1295 userPrivilege = privilege;
1296 break;
1297 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001298 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001299 if (!userPrivilege.empty())
1300 {
1301 break;
1302 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001303 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001304
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001305 if (userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001306 {
1307 log<level::ERR>("LDAP group privilege mapping does not exist");
1308 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001309 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001310 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001311 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001312 {
1313 log<level::ERR>("Error while accessing variant",
1314 entry("WHAT=%s", e.what()));
1315 elog<InternalFailure>();
1316 }
1317 userInfo.emplace("RemoteUser", true);
1318 }
1319
1320 return userInfo;
1321}
1322
Nan Zhou4bc69812022-10-25 00:07:13 +00001323void UserMgr::initializeAccountPolicy()
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301324{
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301325 std::string valueStr;
1326 auto value = minPasswdLength;
1327 unsigned long tmp = 0;
1328 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1329 success)
1330 {
1331 AccountPolicyIface::minPasswordLength(minPasswdLength);
1332 }
1333 else
1334 {
1335 try
1336 {
1337 tmp = std::stoul(valueStr, nullptr);
1338 if (tmp > std::numeric_limits<decltype(value)>::max())
1339 {
1340 throw std::out_of_range("Out of range");
1341 }
1342 value = static_cast<decltype(value)>(tmp);
1343 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001344 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301345 {
1346 log<level::ERR>("Exception for MinPasswordLength",
1347 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001348 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301349 }
1350 AccountPolicyIface::minPasswordLength(value);
1351 }
1352 valueStr.clear();
1353 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1354 success)
1355 {
1356 AccountPolicyIface::rememberOldPasswordTimes(0);
1357 }
1358 else
1359 {
1360 value = 0;
1361 try
1362 {
1363 tmp = std::stoul(valueStr, nullptr);
1364 if (tmp > std::numeric_limits<decltype(value)>::max())
1365 {
1366 throw std::out_of_range("Out of range");
1367 }
1368 value = static_cast<decltype(value)>(tmp);
1369 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001370 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301371 {
1372 log<level::ERR>("Exception for RememberOldPasswordTimes",
1373 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001374 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301375 }
1376 AccountPolicyIface::rememberOldPasswordTimes(value);
1377 }
1378 valueStr.clear();
1379 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1380 {
1381 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1382 }
1383 else
1384 {
1385 uint16_t value16 = 0;
1386 try
1387 {
1388 tmp = std::stoul(valueStr, nullptr);
1389 if (tmp > std::numeric_limits<decltype(value16)>::max())
1390 {
1391 throw std::out_of_range("Out of range");
1392 }
1393 value16 = static_cast<decltype(value16)>(tmp);
1394 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001395 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301396 {
1397 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1398 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001399 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301400 }
1401 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1402 }
1403 valueStr.clear();
1404 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1405 {
1406 AccountPolicyIface::accountUnlockTimeout(0);
1407 }
1408 else
1409 {
1410 uint32_t value32 = 0;
1411 try
1412 {
1413 tmp = std::stoul(valueStr, nullptr);
1414 if (tmp > std::numeric_limits<decltype(value32)>::max())
1415 {
1416 throw std::out_of_range("Out of range");
1417 }
1418 value32 = static_cast<decltype(value32)>(tmp);
1419 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001420 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301421 {
1422 log<level::ERR>("Exception for AccountUnlockTimeout",
1423 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001424 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301425 }
1426 AccountPolicyIface::accountUnlockTimeout(value32);
1427 }
Nan Zhou4bc69812022-10-25 00:07:13 +00001428}
1429
1430void UserMgr::initUserObjects(void)
1431{
1432 // All user management lock has to be based on /etc/shadow
1433 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1434 std::vector<std::string> userNameList;
1435 std::vector<std::string> sshGrpUsersList;
1436 UserSSHLists userSSHLists = getUserAndSshGrpList();
1437 userNameList = std::move(userSSHLists.first);
1438 sshGrpUsersList = std::move(userSSHLists.second);
1439
1440 if (!userNameList.empty())
1441 {
1442 std::map<std::string, std::vector<std::string>> groupLists;
Nan Zhouda401fe2022-10-25 00:07:18 +00001443 // We only track users that are in the |predefinedGroups|
1444 // The other groups don't contain real BMC users.
1445 for (const char* grp : predefinedGroups)
Nan Zhou4bc69812022-10-25 00:07:13 +00001446 {
1447 if (grp == grpSsh)
1448 {
1449 groupLists.emplace(grp, sshGrpUsersList);
1450 }
1451 else
1452 {
1453 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1454 groupLists.emplace(grp, grpUsersList);
1455 }
1456 }
1457 for (auto& grp : privMgr)
1458 {
1459 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1460 groupLists.emplace(grp, grpUsersList);
1461 }
1462
1463 for (auto& user : userNameList)
1464 {
1465 std::vector<std::string> userGroups;
1466 std::string userPriv;
1467 for (const auto& grp : groupLists)
1468 {
1469 std::vector<std::string> tempGrp = grp.second;
1470 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1471 tempGrp.end())
1472 {
1473 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1474 privMgr.end())
1475 {
1476 userPriv = grp.first;
1477 }
1478 else
1479 {
1480 userGroups.emplace_back(grp.first);
1481 }
1482 }
1483 }
1484 // Add user objects to the Users path.
1485 sdbusplus::message::object_path tempObjPath(usersObjPath);
1486 tempObjPath /= user;
1487 std::string objPath(tempObjPath);
1488 std::sort(userGroups.begin(), userGroups.end());
1489 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1490 bus, objPath.c_str(), userGroups,
1491 userPriv, isUserEnabled(user), *this));
1492 }
1493 }
1494}
1495
1496UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1497 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1498 pamPasswdConfigFile(defaultPamPasswdConfigFile),
1499 pamAuthConfigFile(defaultPamAuthConfigFile)
1500{
1501 UserMgrIface::allPrivileges(privMgr);
Nan Zhouda401fe2022-10-25 00:07:18 +00001502 groupsMgr = readAllGroupsOnSystem();
Nan Zhou4bc69812022-10-25 00:07:13 +00001503 std::sort(groupsMgr.begin(), groupsMgr.end());
1504 UserMgrIface::allGroups(groupsMgr);
1505 initializeAccountPolicy();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301506 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301507
1508 // emit the signal
1509 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301510}
1511
Nan Zhou49c81362022-10-25 00:07:08 +00001512void UserMgr::executeUserAdd(const char* userName, const char* groups,
1513 bool sshRequested, bool enabled)
1514{
1515 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1516 // 1970-01-01, that's an implementation-defined behavior
1517 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001518 (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
Nan Zhou49c81362022-10-25 00:07:08 +00001519 (enabled ? "" : "1970-01-01"));
1520}
1521
1522void UserMgr::executeUserDelete(const char* userName)
1523{
1524 executeCmd("/usr/sbin/userdel", userName, "-r");
1525}
1526
Nan Zhouf25443e2022-10-25 00:07:11 +00001527void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1528{
1529 std::string newHomeDir = "/home/";
1530 newHomeDir += newUserName;
1531 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1532 newHomeDir.c_str(), "-m");
1533}
1534
Nan Zhoufef63032022-10-25 00:07:12 +00001535void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1536 bool sshRequested)
1537{
1538 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001539 (sshRequested ? "/bin/sh" : "/sbin/nologin"));
Nan Zhoufef63032022-10-25 00:07:12 +00001540}
1541
Nan Zhou6b6f2d82022-10-25 00:07:17 +00001542void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1543{
1544 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1545 // 1970-01-01, that's an implementation-defined behavior
1546 executeCmd("/usr/sbin/usermod", userName, "-e",
1547 (enabled ? "" : "1970-01-01"));
1548}
1549
Nan Zhoua2953032022-11-11 21:50:32 +00001550std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1551{
1552 return executeCmd("/usr/sbin/pam_tally2", "-u", userName);
1553}
1554
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301555} // namespace user
1556} // namespace phosphor