blob: 9cc68e194b4a731c736673a58f6dd6164b657804 [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>
Jiaqing Zhao11ec6662022-07-05 20:55:34 +080036#include <phosphor-logging/lg2.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* pamPWHistory = "pam_pwhistory.so";
65static constexpr const char* minPasswdLenProp = "minlen";
66static constexpr const char* remOldPasswdCount = "remember";
67static constexpr const char* maxFailedAttempt = "deny";
68static constexpr const char* unlockTimeout = "unlock_time";
Nan Zhoue48085d2022-10-25 00:07:04 +000069static constexpr const char* defaultPamPasswdConfigFile =
70 "/etc/pam.d/common-password";
Jason M. Bills2d042d12023-03-28 15:32:45 -070071static constexpr const char* defaultFaillockConfigFile =
72 "/etc/security/faillock.conf";
73static constexpr const char* defaultPWQualityConfigFile =
74 "/etc/security/pwquality.conf";
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
Ninad Palsule601d3db2023-03-09 10:27:37 -0600113constexpr std::array<const char*, 5> predefinedGroups = {
114 "web", "redfish", "ipmi", "ssh", "hostconsole"};
Nan Zhouda401fe2022-10-25 00:07:18 +0000115
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 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800141 lg2::error("Group name '{GROUP}' is not in the allowed list", "GROUP",
142 groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000143 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) {
Patrick Williamsb7043042023-05-10 07:50:52 -0500158 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 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800187 lg2::error("User name is empty");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530188 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 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800202 lg2::error("User '{USERNAME}' does not exist", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530203 elog<UserNameDoesNotExist>();
204 }
205}
206
Nan Zhouda401fe2022-10-25 00:07:18 +0000207void UserMgr::checkAndThrowForDisallowedGroupCreation(
208 const std::string& groupName)
209{
210 if (groupName.size() > maxSystemGroupNameLength ||
211 !std::regex_match(groupName.c_str(),
212 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
213 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800214 lg2::error("Invalid group name '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000215 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
216 Argument::ARGUMENT_VALUE(groupName.c_str()));
217 }
218 checkAndThrowsForGroupChangeAllowed(groupName);
219}
220
Patrick Williams9638afb2021-02-22 17:16:24 -0600221void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530222{
Nan Zhou8a11d992022-10-25 00:07:06 +0000223 if (isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530224 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800225 lg2::error("User '{USERNAME}' already exists", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530226 elog<UserNameExists>();
227 }
228}
229
230void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600231 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530232{
233 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
234 groupNames.end())
235 {
236 if (userName.length() > ipmiMaxUserNameLen)
237 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800238 lg2::error("User '{USERNAME}' exceeds IPMI username length limit "
239 "({LENGTH} > {LIMIT})",
240 "USERNAME", userName, "LENGTH", userName.length(),
241 "LIMIT", ipmiMaxUserNameLen);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530242 elog<UserNameGroupFail>(
243 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
244 "IPMI length"));
245 }
246 }
247 if (userName.length() > systemMaxUserNameLen)
248 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800249 lg2::error("User '{USERNAME}' exceeds system username length limit "
250 "({LENGTH} > {LIMIT})",
251 "USERNAME", userName, "LENGTH", userName.length(), "LIMIT",
252 systemMaxUserNameLen);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530253 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
254 Argument::ARGUMENT_VALUE("Invalid length"));
255 }
256 if (!std::regex_match(userName.c_str(),
257 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
258 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800259 lg2::error("Invalid username '{USERNAME}'", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530260 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 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800273 lg2::error("IPMI user limit reached");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530274 elog<NoResource>(
275 xyz::openbmc_project::User::Common::NoResource::REASON(
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800276 "IPMI user limit reached"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530277 }
278 }
279 else
280 {
281 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
282 (maxSystemUsers - ipmiMaxUsers))
283 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800284 lg2::error("Non-ipmi User limit reached");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530285 elog<NoResource>(
286 xyz::openbmc_project::User::Common::NoResource::REASON(
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800287 "Non-ipmi user limit reached"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530288 }
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 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800298 lg2::error("Invalid privilege '{PRIVILEGE}'", "PRIVILEGE", priv);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530299 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 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800311 lg2::error("Invalid Group Name '{GROUPNAME}'", "GROUPNAME", group);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530312 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 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800373 lg2::error("Unable to create new user '{USERNAME}'", "USERNAME",
374 userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530375 elog<InternalFailure>();
376 }
377
378 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530379 sdbusplus::message::object_path tempObjPath(usersObjPath);
380 tempObjPath /= userName;
381 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530382 std::sort(groupNames.begin(), groupNames.end());
383 usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +0000384 userName, std::make_unique<phosphor::user::Users>(
385 bus, userObj.c_str(), groupNames, priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530386
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800387 lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530388 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());
Jayanth Othayothac921a52023-07-21 03:48:55 -0500399
400 // Clear user fail records
401 executeUserClearFailRecords(userName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530402 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600403 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530404 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800405 lg2::error("Delete User '{USERNAME}' failed", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530406 elog<InternalFailure>();
407 }
408
409 usersList.erase(userName);
410
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800411 lg2::info("User '{USERNAME}' deleted successfully", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530412 return;
413}
414
Nan Zhouda401fe2022-10-25 00:07:18 +0000415void UserMgr::checkDeleteGroupConstraints(const std::string& groupName)
416{
417 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) ==
418 groupsMgr.end())
419 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800420 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000421 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 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800435 lg2::error("Failed to delete group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000436 elog<InternalFailure>();
437 }
438
439 groupsMgr.erase(std::find(groupsMgr.begin(), groupsMgr.end(), groupName));
440 UserMgrIface::allGroups(groupsMgr);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800441 lg2::info("Successfully deleted group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000442}
443
444void UserMgr::checkCreateGroupConstraints(const std::string& groupName)
445{
446 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) !=
447 groupsMgr.end())
448 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800449 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000450 elog<GroupNameExists>();
451 }
452 checkAndThrowForDisallowedGroupCreation(groupName);
453 if (groupsMgr.size() >= maxSystemGroupCount)
454 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800455 lg2::error("Group limit reached");
Nan Zhouda401fe2022-10-25 00:07:18 +0000456 elog<NoResource>(xyz::openbmc_project::User::Common::NoResource::REASON(
457 "Group limit reached"));
458 }
459}
460
461void UserMgr::createGroup(std::string groupName)
462{
463 checkCreateGroupConstraints(groupName);
464 try
465 {
466 executeGroupCreation(groupName.c_str());
467 }
468 catch (const InternalFailure& e)
469 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800470 lg2::error("Failed to create group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000471 elog<InternalFailure>();
472 }
473 groupsMgr.push_back(groupName);
474 UserMgrIface::allGroups(groupsMgr);
475}
476
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530477void UserMgr::renameUser(std::string userName, std::string newUserName)
478{
479 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500480 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530481 throwForUserDoesNotExist(userName);
482 throwForUserExists(newUserName);
483 throwForUserNameConstraints(newUserName,
484 usersList[userName].get()->userGroups());
485 try
486 {
Nan Zhouf25443e2022-10-25 00:07:11 +0000487 executeUserRename(userName.c_str(), newUserName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530488 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600489 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530490 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800491 lg2::error("Rename '{USERNAME}' to '{NEWUSERNAME}' failed", "USERNAME",
492 userName, "NEWUSERNAME", newUserName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530493 elog<InternalFailure>();
494 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600495 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530496 std::string priv = user.get()->userPrivilege();
497 std::vector<std::string> groupNames = user.get()->userGroups();
498 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530499 sdbusplus::message::object_path tempObjPath(usersObjPath);
500 tempObjPath /= newUserName;
501 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530502 // Special group 'ipmi' needs a way to identify user renamed, in order to
503 // update encrypted password. It can't rely only on InterfacesRemoved &
504 // InterfacesAdded. So first send out userRenamed signal.
505 this->userRenamed(userName, newUserName);
506 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000507 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
508 bus, newUserObj.c_str(), groupNames,
509 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530510 return;
511}
512
Patrick Williams9638afb2021-02-22 17:16:24 -0600513void UserMgr::updateGroupsAndPriv(const std::string& userName,
Nan Zhoufef63032022-10-25 00:07:12 +0000514 std::vector<std::string> groupNames,
Patrick Williams9638afb2021-02-22 17:16:24 -0600515 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530516{
517 throwForInvalidPrivilege(priv);
518 throwForInvalidGroups(groupNames);
519 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500520 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530521 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600522 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530523 usersList[userName].get()->userGroups();
524 std::vector<std::string> groupDiff;
525 // Note: already dealing with sorted group lists.
526 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
527 groupNames.begin(), groupNames.end(),
528 std::back_inserter(groupDiff));
529 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
530 groupDiff.end())
531 {
532 throwForUserNameConstraints(userName, groupNames);
533 throwForMaxGrpUserCount(groupNames);
534 }
535
536 std::string groups = getCSVFromVector(groupNames);
537 bool sshRequested = removeStringFromCSV(groups, grpSsh);
538
539 // treat privilege as a group - This is to avoid using different file to
540 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530541 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530542 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530543 if (groups.size() != 0)
544 {
545 groups += ",";
546 }
547 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530548 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530549 try
550 {
Nan Zhoufef63032022-10-25 00:07:12 +0000551 executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530552 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600553 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530554 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800555 lg2::error(
556 "Unable to modify user privilege / groups for user '{USERNAME}'",
557 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530558 elog<InternalFailure>();
559 }
560
Nan Zhoufef63032022-10-25 00:07:12 +0000561 std::sort(groupNames.begin(), groupNames.end());
562 usersList[userName]->setUserGroups(groupNames);
563 usersList[userName]->setUserPrivilege(priv);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800564 lg2::info("User '{USERNAME}' groups / privilege updated successfully",
565 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530566}
567
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530568uint8_t UserMgr::minPasswordLength(uint8_t value)
569{
570 if (value == AccountPolicyIface::minPasswordLength())
571 {
572 return value;
573 }
574 if (value < minPasswdLength)
575 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800576 lg2::error("Attempting to set minPasswordLength to {VALUE}, less than "
577 "{MINVALUE}",
578 "VALUE", value, "MINVALUE", minPasswdLength);
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000579 elog<InvalidArgument>(
580 Argument::ARGUMENT_NAME("minPasswordLength"),
581 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530582 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700583 if (setPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
584 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530585 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800586 lg2::error("Unable to set minPasswordLength to {VALUE}", "VALUE",
587 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530588 elog<InternalFailure>();
589 }
590 return AccountPolicyIface::minPasswordLength(value);
591}
592
593uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
594{
595 if (value == AccountPolicyIface::rememberOldPasswordTimes())
596 {
597 return value;
598 }
599 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
600 std::to_string(value)) != success)
601 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800602 lg2::error("Unable to set rememberOldPasswordTimes to {VALUE}", "VALUE",
603 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530604 elog<InternalFailure>();
605 }
606 return AccountPolicyIface::rememberOldPasswordTimes(value);
607}
608
609uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
610{
611 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
612 {
613 return value;
614 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700615 if (setPamModuleConfValue(faillockConfigFile, maxFailedAttempt,
616 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530617 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800618 lg2::error("Unable to set maxLoginAttemptBeforeLockout to {VALUE}",
619 "VALUE", value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530620 elog<InternalFailure>();
621 }
622 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
623}
624
625uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
626{
627 if (value == AccountPolicyIface::accountUnlockTimeout())
628 {
629 return value;
630 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700631 if (setPamModuleConfValue(faillockConfigFile, unlockTimeout,
632 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530633 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800634 lg2::error("Unable to set accountUnlockTimeout to {VALUE}", "VALUE",
635 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530636 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{
Jason M. Bills2d042d12023-03-28 15:32:45 -0700645 std::string fileName = pamPasswdConfigFile;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530646 std::ifstream fileToRead(fileName, std::ios::in);
647 if (!fileToRead.is_open())
648 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800649 lg2::error("Failed to open pam configuration file {FILENAME}",
650 "FILENAME", fileName);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530651 return failure;
652 }
653 std::string line;
654 auto argSearch = argName + "=";
655 size_t startPos = 0;
656 size_t endPos = 0;
657 while (getline(fileToRead, line))
658 {
659 // skip comments section starting with #
660 if ((startPos = line.find('#')) != std::string::npos)
661 {
662 if (startPos == 0)
663 {
664 continue;
665 }
666 // skip comments after meaningful section and process those
667 line = line.substr(0, startPos);
668 }
669 if (line.find(moduleName) != std::string::npos)
670 {
671 if ((startPos = line.find(argSearch)) != std::string::npos)
672 {
673 if ((endPos = line.find(' ', startPos)) == std::string::npos)
674 {
675 endPos = line.size();
676 }
677 startPos += argSearch.size();
678 argValue = line.substr(startPos, endPos - startPos);
679 return success;
680 }
681 }
682 }
683 return failure;
684}
685
Jason M. Bills2d042d12023-03-28 15:32:45 -0700686int UserMgr::getPamModuleConfValue(const std::string& confFile,
687 const std::string& argName,
688 std::string& argValue)
689{
690 std::ifstream fileToRead(confFile, std::ios::in);
691 if (!fileToRead.is_open())
692 {
693 lg2::error("Failed to open pam configuration file {FILENAME}",
694 "FILENAME", confFile);
695 return failure;
696 }
697 std::string line;
698 auto argSearch = argName + "=";
699 size_t startPos = 0;
700 size_t endPos = 0;
701 while (getline(fileToRead, line))
702 {
703 // skip comments section starting with #
704 if ((startPos = line.find('#')) != std::string::npos)
705 {
706 if (startPos == 0)
707 {
708 continue;
709 }
710 // skip comments after meaningful section and process those
711 line = line.substr(0, startPos);
712 }
713 if ((startPos = line.find(argSearch)) != std::string::npos)
714 {
715 if ((endPos = line.find(' ', startPos)) == std::string::npos)
716 {
717 endPos = line.size();
718 }
719 startPos += argSearch.size();
720 argValue = line.substr(startPos, endPos - startPos);
721 return success;
722 }
723 }
724 return failure;
725}
726
Patrick Williams9638afb2021-02-22 17:16:24 -0600727int UserMgr::setPamModuleArgValue(const std::string& moduleName,
728 const std::string& argName,
729 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530730{
Jason M. Bills2d042d12023-03-28 15:32:45 -0700731 std::string fileName = pamPasswdConfigFile;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530732 std::string tmpFileName = fileName + "_tmp";
733 std::ifstream fileToRead(fileName, std::ios::in);
734 std::ofstream fileToWrite(tmpFileName, std::ios::out);
735 if (!fileToRead.is_open() || !fileToWrite.is_open())
736 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800737 lg2::error("Failed to open pam configuration file {FILENAME}",
738 "FILENAME", fileName);
Jason M. Bills17b88272023-05-22 14:24:02 -0700739 // Delete the unused tmp file
740 std::remove(tmpFileName.c_str());
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530741 return failure;
742 }
743 std::string line;
744 auto argSearch = argName + "=";
745 size_t startPos = 0;
746 size_t endPos = 0;
747 bool found = false;
748 while (getline(fileToRead, line))
749 {
750 // skip comments section starting with #
751 if ((startPos = line.find('#')) != std::string::npos)
752 {
753 if (startPos == 0)
754 {
755 fileToWrite << line << std::endl;
756 continue;
757 }
758 // skip comments after meaningful section and process those
759 line = line.substr(0, startPos);
760 }
761 if (line.find(moduleName) != std::string::npos)
762 {
763 if ((startPos = line.find(argSearch)) != std::string::npos)
764 {
765 if ((endPos = line.find(' ', startPos)) == std::string::npos)
766 {
767 endPos = line.size();
768 }
769 startPos += argSearch.size();
770 fileToWrite << line.substr(0, startPos) << argValue
771 << line.substr(endPos, line.size() - endPos)
772 << std::endl;
773 found = true;
774 continue;
775 }
776 }
777 fileToWrite << line << std::endl;
778 }
779 fileToWrite.close();
780 fileToRead.close();
781 if (found)
782 {
783 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
784 {
785 return success;
786 }
787 }
Jason M. Bills17b88272023-05-22 14:24:02 -0700788 // No changes, so delete the unused tmp file
789 std::remove(tmpFileName.c_str());
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530790 return failure;
791}
792
Jason M. Bills2d042d12023-03-28 15:32:45 -0700793int UserMgr::setPamModuleConfValue(const std::string& confFile,
794 const std::string& argName,
795 const std::string& argValue)
796{
797 std::string tmpConfFile = confFile + "_tmp";
798 std::ifstream fileToRead(confFile, std::ios::in);
799 std::ofstream fileToWrite(tmpConfFile, std::ios::out);
800 if (!fileToRead.is_open() || !fileToWrite.is_open())
801 {
802 lg2::error("Failed to open pam configuration file {FILENAME}",
803 "FILENAME", confFile);
Jason M. Bills17b88272023-05-22 14:24:02 -0700804 // Delete the unused tmp file
805 std::remove(tmpConfFile.c_str());
Jason M. Bills2d042d12023-03-28 15:32:45 -0700806 return failure;
807 }
808 std::string line;
809 auto argSearch = argName + "=";
810 size_t startPos = 0;
811 size_t endPos = 0;
812 bool found = false;
813 while (getline(fileToRead, line))
814 {
815 // skip comments section starting with #
816 if ((startPos = line.find('#')) != std::string::npos)
817 {
818 if (startPos == 0)
819 {
820 fileToWrite << line << std::endl;
821 continue;
822 }
823 // skip comments after meaningful section and process those
824 line = line.substr(0, startPos);
825 }
826 if ((startPos = line.find(argSearch)) != std::string::npos)
827 {
828 if ((endPos = line.find(' ', startPos)) == std::string::npos)
829 {
830 endPos = line.size();
831 }
832 startPos += argSearch.size();
833 fileToWrite << line.substr(0, startPos) << argValue
834 << line.substr(endPos, line.size() - endPos)
835 << std::endl;
836 found = true;
837 continue;
838 }
839 fileToWrite << line << std::endl;
840 }
841 fileToWrite.close();
842 fileToRead.close();
843 if (found)
844 {
845 if (std::rename(tmpConfFile.c_str(), confFile.c_str()) == 0)
846 {
847 return success;
848 }
849 }
Jason M. Bills17b88272023-05-22 14:24:02 -0700850 // No changes, so delete the unused tmp file
851 std::remove(tmpConfFile.c_str());
Jason M. Bills2d042d12023-03-28 15:32:45 -0700852 return failure;
853}
854
Patrick Williams9638afb2021-02-22 17:16:24 -0600855void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530856{
857 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500858 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530859 throwForUserDoesNotExist(userName);
860 try
861 {
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000862 executeUserModifyUserEnable(userName.c_str(), enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530863 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600864 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530865 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800866 lg2::error("Unable to modify user enabled state for '{USERNAME}'",
867 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530868 elog<InternalFailure>();
869 }
870
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000871 usersList[userName]->setUserEnabled(enabled);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800872 lg2::info("User '{USERNAME}' has been {STATUS}", "USERNAME", userName,
873 "STATUS", enabled ? "Enabled" : "Disabled");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530874}
875
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530876/**
Jason M. Bills2d042d12023-03-28 15:32:45 -0700877 * faillock app will provide the user failed login list with when the attempt
878 * was made, the type, the source, and if it's valid.
879 *
880 * Valid in this case means that the attempt was made within the fail_interval
881 * time. So, we can check this list for the number of valid entries (lines
882 * ending with 'V') compared to the maximum allowed to determine if the user is
883 * locked out.
884 *
885 * This data is only refreshed when an attempt is made, so if the user appears
886 * to be locked out, we must also check if the most recent attempt was older
887 * than the unlock_time to know if the user has since been unlocked.
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530888 **/
Jason M. Bills2d042d12023-03-28 15:32:45 -0700889bool UserMgr::parseFaillockForLockout(
890 const std::vector<std::string>& faillockOutput)
891{
892 uint16_t failAttempts = 0;
893 time_t lastFailedAttempt{};
894 for (const std::string& line : faillockOutput)
895 {
896 if (!line.ends_with("V"))
897 {
898 continue;
899 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530900
Jason M. Bills2d042d12023-03-28 15:32:45 -0700901 // Count this failed attempt
902 failAttempts++;
903
904 // Update the last attempt time
905 // First get the "when" which is the first two words (date and time)
906 size_t pos = line.find(" ");
907 if (pos == std::string::npos)
908 {
909 continue;
910 }
911 pos = line.find(" ", pos + 1);
912 if (pos == std::string::npos)
913 {
914 continue;
915 }
916 std::string failDateTime = line.substr(0, pos);
917
918 // NOTE: Cannot use std::get_time() here as the implementation of %y in
919 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
920 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
921 std::tm tmStruct = {};
922 if (!strptime(failDateTime.c_str(), "%F %T", &tmStruct))
923 {
924 lg2::error("Failed to parse latest failure date/time");
925 elog<InternalFailure>();
926 }
927
928 time_t failTimestamp = std::mktime(&tmStruct);
929 lastFailedAttempt = std::max(failTimestamp, lastFailedAttempt);
930 }
931
932 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
933 {
934 return false;
935 }
936
937 if (lastFailedAttempt +
938 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
939 std::time(NULL))
940 {
941 return false;
942 }
943
944 return true;
945}
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530946
Patrick Williams9638afb2021-02-22 17:16:24 -0600947bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530948{
949 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500950 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800951 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
952 {
953 return false;
954 }
955
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530956 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800957 try
958 {
Nan Zhoua2953032022-11-11 21:50:32 +0000959 output = getFailedAttempt(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800960 }
961 catch (const InternalFailure& e)
962 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800963 lg2::error("Unable to read login failure counter");
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800964 elog<InternalFailure>();
965 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530966
Jason M. Bills2d042d12023-03-28 15:32:45 -0700967 return parseFaillockForLockout(output);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530968}
969
Patrick Williams9638afb2021-02-22 17:16:24 -0600970bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
971 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530972{
973 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500974 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530975 if (value == true)
976 {
977 return userLockedForFailedAttempt(userName);
978 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530979
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800980 try
981 {
Jayanth Othayothac921a52023-07-21 03:48:55 -0500982 // Clear user fail records
983 executeUserClearFailRecords(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800984 }
985 catch (const InternalFailure& e)
986 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800987 lg2::error("Unable to reset login failure counter");
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800988 elog<InternalFailure>();
989 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530990
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530991 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530992}
993
Patrick Williams9638afb2021-02-22 17:16:24 -0600994bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600995{
996 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500997 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600998
999 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -06001000 {};
1001 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001002 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1003 if (buflen < -1)
1004 {
1005 // Use a default size if there is no hard limit suggested by sysconf()
1006 buflen = 1024;
1007 }
1008 std::vector<char> buffer(buflen);
Patrick Williamsb7043042023-05-10 07:50:52 -05001009 auto status = getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen,
1010 &spwdPtr);
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001011 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
1012 // If no matching password record was found, these functions return 0
1013 // and store NULL in *spwdPtr
1014 if ((status == 0) && (&spwd == spwdPtr))
1015 {
1016 // Determine password validity per "chage" docs, where:
1017 // spwd.sp_lstchg == 0 means password is expired, and
1018 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +00001019 constexpr long secondsPerDay = 60 * 60 * 24;
1020 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001021 if ((spwd.sp_lstchg == 0) ||
1022 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
1023 {
1024 return true;
1025 }
1026 }
1027 else
1028 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +00001029 // User entry is missing in /etc/shadow, indicating no SHA password.
1030 // Treat this as new user without password entry in /etc/shadow
1031 // TODO: Add property to indicate user password was not set yet
1032 // https://github.com/openbmc/phosphor-user-manager/issues/8
1033 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001034 }
1035
1036 return false;
1037}
1038
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301039UserSSHLists UserMgr::getUserAndSshGrpList()
1040{
1041 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001042 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301043
1044 std::vector<std::string> userList;
1045 std::vector<std::string> sshUsersList;
1046 struct passwd pw, *pwp = nullptr;
1047 std::array<char, 1024> buffer{};
1048
1049 phosphor::user::File passwd(passwdFileName, "r");
1050 if ((passwd)() == NULL)
1051 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001052 lg2::error("Error opening {FILENAME}", "FILENAME", passwdFileName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301053 elog<InternalFailure>();
1054 }
1055
1056 while (true)
1057 {
1058 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
1059 &pwp);
1060 if ((r != 0) || (pwp == NULL))
1061 {
1062 // Any error, break the loop.
1063 break;
1064 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +05301065#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +05301066 // Add all users whose UID >= 1000 and < 65534
1067 // and special UID 0.
1068 if ((pwp->pw_uid == 0) ||
1069 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +05301070#else
1071 // Add all users whose UID >=1000 and < 65534
1072 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
1073#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301074 {
1075 std::string userName(pwp->pw_name);
1076 userList.emplace_back(userName);
1077
1078 // ssh doesn't have separate group. Check login shell entry to
1079 // get all users list which are member of ssh group.
1080 std::string loginShell(pwp->pw_shell);
1081 if (loginShell == "/bin/sh")
1082 {
1083 sshUsersList.emplace_back(userName);
1084 }
1085 }
1086 }
1087 endpwent();
1088 return std::make_pair(std::move(userList), std::move(sshUsersList));
1089}
1090
1091size_t UserMgr::getIpmiUsersCount()
1092{
1093 std::vector<std::string> userList = getUsersInGroup("ipmi");
1094 return userList.size();
1095}
1096
Nan Zhou49c81362022-10-25 00:07:08 +00001097size_t UserMgr::getNonIpmiUsersCount()
1098{
1099 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
1100 return usersList.size() - ipmiUsers.size();
1101}
1102
Patrick Williams9638afb2021-02-22 17:16:24 -06001103bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301104{
1105 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001106 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301107 std::array<char, 4096> buffer{};
1108 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -06001109 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301110 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
1111 buffer.max_size(), &resultPtr);
1112 if (!status && (&spwd == resultPtr))
1113 {
1114 if (resultPtr->sp_expire >= 0)
1115 {
1116 return false; // user locked out
1117 }
1118 return true;
1119 }
1120 return false; // assume user is disabled for any error.
1121}
1122
Patrick Williams9638afb2021-02-22 17:16:24 -06001123std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301124{
1125 std::vector<std::string> usersInGroup;
1126 // Should be more than enough to get the pwd structure.
1127 std::array<char, 4096> buffer{};
1128 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -06001129 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301130
1131 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1132 buffer.max_size(), &resultPtr);
1133
1134 if (!status && (&grp == resultPtr))
1135 {
1136 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
1137 {
1138 usersInGroup.emplace_back(*(grp.gr_mem));
1139 }
1140 }
1141 else
1142 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001143 lg2::error("Group '{GROUPNAME}' not found", "GROUPNAME", groupName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301144 // Don't throw error, just return empty userList - fallback
1145 }
1146 return usersInGroup;
1147}
1148
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001149DbusUserObj UserMgr::getPrivilegeMapperObject(void)
1150{
1151 DbusUserObj objects;
1152 try
1153 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001154 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
1155 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001156
Patrick Williamsb7043042023-05-10 07:50:52 -05001157 auto ldapMgmtService = getServiceName(std::move(basePath),
1158 std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001159 auto method = bus.new_method_call(
1160 ldapMgmtService.c_str(), ldapMgrObjBasePath,
1161 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1162
1163 auto reply = bus.call(method);
1164 reply.read(objects);
1165 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001166 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001167 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001168 lg2::error("Unable to get the User Service: {ERR}", "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001169 throw;
1170 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001171 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001172 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001173 lg2::error("Failed to excute GetManagedObjects at {PATH}: {ERR}",
1174 "PATH", ldapMgrObjBasePath, "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001175 throw;
1176 }
1177 return objects;
1178}
1179
Patrick Williams9638afb2021-02-22 17:16:24 -06001180std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001181{
1182 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1183 objMapperInterface, "GetObject");
1184
1185 mapperCall.append(std::move(path));
1186 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1187
1188 auto mapperResponseMsg = bus.call(mapperCall);
1189
1190 if (mapperResponseMsg.is_method_error())
1191 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001192 lg2::error("Error in mapper call");
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001193 elog<InternalFailure>();
1194 }
1195
1196 std::map<std::string, std::vector<std::string>> mapperResponse;
1197 mapperResponseMsg.read(mapperResponse);
1198
1199 if (mapperResponse.begin() == mapperResponse.end())
1200 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001201 lg2::error("Invalid response from mapper");
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001202 elog<InternalFailure>();
1203 }
1204
1205 return mapperResponse.begin()->first;
1206}
1207
Alexander Filippov75626582022-02-09 18:42:37 +03001208gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
1209{
1210 static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1211 if (buflen <= 0)
1212 {
1213 // Use a default size if there is no hard limit suggested by sysconf()
1214 buflen = 1024;
1215 }
1216
1217 struct passwd pwd;
1218 struct passwd* pwdPtr = nullptr;
1219 std::vector<char> buffer(buflen);
1220
1221 auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(),
1222 buffer.size(), &pwdPtr);
1223 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
1224 // If no matching password record was found, these functions return 0
1225 // and store NULL in *pwdPtr
1226 if (!status && (&pwd == pwdPtr))
1227 {
1228 return pwd.pw_gid;
1229 }
1230
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001231 lg2::error("User {USERNAME} does not exist", "USERNAME", userName);
Alexander Filippov75626582022-02-09 18:42:37 +03001232 elog<UserNameDoesNotExist>();
1233}
1234
1235bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
1236 const std::string& groupName) const
1237{
1238 static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1239 if (buflen <= 0)
1240 {
1241 // Use a default size if there is no hard limit suggested by sysconf()
1242 buflen = 1024;
1243 }
1244
1245 struct group grp;
1246 struct group* grpPtr = nullptr;
1247 std::vector<char> buffer(buflen);
1248
1249 auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1250 buffer.size(), &grpPtr);
1251
1252 // Groups with a lot of members may require a buffer of bigger size than
1253 // suggested by _SC_GETGR_R_SIZE_MAX.
1254 // 32K should be enough for about 2K members.
1255 constexpr auto maxBufferLength = 32 * 1024;
1256 while (status == ERANGE && buflen < maxBufferLength)
1257 {
1258 buflen *= 2;
1259 buffer.resize(buflen);
1260
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001261 lg2::debug("Increase buffer for getgrnam_r() to {SIZE}", "SIZE",
1262 buflen);
Alexander Filippov75626582022-02-09 18:42:37 +03001263
1264 status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1265 buffer.size(), &grpPtr);
1266 }
1267
1268 // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
1269 // If no matching group record was found, these functions return 0
1270 // and store NULL in *grpPtr
1271 if (!status && (&grp == grpPtr))
1272 {
1273 if (primaryGid == grp.gr_gid)
1274 {
1275 return true;
1276 }
1277
1278 for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
1279 {
1280 if (userName == grp.gr_mem[i])
1281 {
1282 return true;
1283 }
1284 }
1285 }
1286 else if (status == ERANGE)
1287 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001288 lg2::error("Group info of {GROUP} requires too much memory", "GROUP",
1289 groupName);
Alexander Filippov75626582022-02-09 18:42:37 +03001290 }
1291 else
1292 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001293 lg2::error("Group {GROUP} does not exist", "GROUP", groupName);
Alexander Filippov75626582022-02-09 18:42:37 +03001294 }
1295
1296 return false;
1297}
1298
Nan Zhouda401fe2022-10-25 00:07:18 +00001299void UserMgr::executeGroupCreation(const char* groupName)
1300{
1301 executeCmd("/usr/sbin/groupadd", groupName);
1302}
1303
1304void UserMgr::executeGroupDeletion(const char* groupName)
1305{
1306 executeCmd("/usr/sbin/groupdel", groupName);
1307}
1308
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001309UserInfoMap UserMgr::getUserInfo(std::string userName)
1310{
1311 UserInfoMap userInfo;
1312 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001313 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001314 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001315 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001316 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1317 userInfo.emplace("UserGroups", user.get()->userGroups());
1318 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1319 userInfo.emplace("UserLockedForFailedAttempt",
1320 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001321 userInfo.emplace("UserPasswordExpired",
1322 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001323 userInfo.emplace("RemoteUser", false);
1324 }
1325 else
1326 {
Alexander Filippov75626582022-02-09 18:42:37 +03001327 auto primaryGid = getPrimaryGroup(userName);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001328
1329 DbusUserObj objects = getPrivilegeMapperObject();
1330
Ravi Teja5fe724a2019-05-07 05:14:42 -05001331 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001332 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001333
1334 try
1335 {
Alexander Filippov75626582022-02-09 18:42:37 +03001336 for (const auto& [path, interfaces] : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001337 {
Alexander Filippov75626582022-02-09 18:42:37 +03001338 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
1339 if (it != interfaces.end())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001340 {
Alexander Filippov75626582022-02-09 18:42:37 +03001341 auto propIt = it->second.find("Enabled");
1342 if (propIt != it->second.end() &&
1343 std::get<bool>(propIt->second))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001344 {
Alexander Filippov75626582022-02-09 18:42:37 +03001345 ldapConfigPath = path.str + '/';
1346 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001347 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001348 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001349 }
1350
1351 if (ldapConfigPath.empty())
1352 {
1353 return userInfo;
1354 }
1355
Alexander Filippov75626582022-02-09 18:42:37 +03001356 for (const auto& [path, interfaces] : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001357 {
Alexander Filippov75626582022-02-09 18:42:37 +03001358 if (!path.str.starts_with(ldapConfigPath))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001359 {
Alexander Filippov75626582022-02-09 18:42:37 +03001360 continue;
1361 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001362
Alexander Filippov75626582022-02-09 18:42:37 +03001363 auto it = interfaces.find(
1364 "xyz.openbmc_project.User.PrivilegeMapperEntry");
1365 if (it != interfaces.end())
1366 {
1367 std::string privilege;
1368 std::string groupName;
1369
1370 for (const auto& [propName, propValue] : it->second)
1371 {
1372 if (propName == "GroupName")
Ravi Teja5fe724a2019-05-07 05:14:42 -05001373 {
Alexander Filippov75626582022-02-09 18:42:37 +03001374 groupName = std::get<std::string>(propValue);
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001375 }
Alexander Filippov75626582022-02-09 18:42:37 +03001376 else if (propName == "Privilege")
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001377 {
Alexander Filippov75626582022-02-09 18:42:37 +03001378 privilege = std::get<std::string>(propValue);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001379 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001380 }
Alexander Filippov75626582022-02-09 18:42:37 +03001381
1382 if (!groupName.empty() && !privilege.empty() &&
1383 isGroupMember(userName, primaryGid, groupName))
1384 {
1385 userPrivilege = privilege;
1386 break;
1387 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001388 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001389 if (!userPrivilege.empty())
1390 {
1391 break;
1392 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001393 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001394
Jiaqing Zhao56862062022-05-31 19:16:09 +08001395 if (!userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001396 {
Jiaqing Zhao56862062022-05-31 19:16:09 +08001397 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001398 }
Jiaqing Zhao56862062022-05-31 19:16:09 +08001399 else
1400 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001401 lg2::warning("LDAP group privilege mapping does not exist, "
1402 "default \"priv-user\" is used");
Jiaqing Zhao56862062022-05-31 19:16:09 +08001403 userInfo.emplace("UserPrivilege", "priv-user");
1404 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001405 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001406 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001407 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001408 lg2::error("Error while accessing variant: {ERR}", "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001409 elog<InternalFailure>();
1410 }
1411 userInfo.emplace("RemoteUser", true);
1412 }
1413
1414 return userInfo;
1415}
1416
Nan Zhou4bc69812022-10-25 00:07:13 +00001417void UserMgr::initializeAccountPolicy()
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301418{
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301419 std::string valueStr;
1420 auto value = minPasswdLength;
1421 unsigned long tmp = 0;
Jason M. Bills2d042d12023-03-28 15:32:45 -07001422 if (getPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
1423 valueStr) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301424 {
1425 AccountPolicyIface::minPasswordLength(minPasswdLength);
1426 }
1427 else
1428 {
1429 try
1430 {
1431 tmp = std::stoul(valueStr, nullptr);
1432 if (tmp > std::numeric_limits<decltype(value)>::max())
1433 {
1434 throw std::out_of_range("Out of range");
1435 }
1436 value = static_cast<decltype(value)>(tmp);
1437 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001438 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301439 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001440 lg2::error("Exception for MinPasswordLength: {ERR}", "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001441 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301442 }
1443 AccountPolicyIface::minPasswordLength(value);
1444 }
1445 valueStr.clear();
1446 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1447 success)
1448 {
1449 AccountPolicyIface::rememberOldPasswordTimes(0);
1450 }
1451 else
1452 {
1453 value = 0;
1454 try
1455 {
1456 tmp = std::stoul(valueStr, nullptr);
1457 if (tmp > std::numeric_limits<decltype(value)>::max())
1458 {
1459 throw std::out_of_range("Out of range");
1460 }
1461 value = static_cast<decltype(value)>(tmp);
1462 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001463 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301464 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001465 lg2::error("Exception for RememberOldPasswordTimes: {ERR}", "ERR",
1466 e);
Patrick Venture045b1122018-10-16 15:59:29 -07001467 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301468 }
1469 AccountPolicyIface::rememberOldPasswordTimes(value);
1470 }
1471 valueStr.clear();
Jason M. Bills2d042d12023-03-28 15:32:45 -07001472 if (getPamModuleConfValue(faillockConfigFile, maxFailedAttempt, valueStr) !=
1473 success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301474 {
1475 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1476 }
1477 else
1478 {
1479 uint16_t value16 = 0;
1480 try
1481 {
1482 tmp = std::stoul(valueStr, nullptr);
1483 if (tmp > std::numeric_limits<decltype(value16)>::max())
1484 {
1485 throw std::out_of_range("Out of range");
1486 }
1487 value16 = static_cast<decltype(value16)>(tmp);
1488 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001489 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301490 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001491 lg2::error("Exception for MaxLoginAttemptBeforLockout: {ERR}",
1492 "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001493 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301494 }
1495 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1496 }
1497 valueStr.clear();
Jason M. Bills2d042d12023-03-28 15:32:45 -07001498 if (getPamModuleConfValue(faillockConfigFile, unlockTimeout, valueStr) !=
1499 success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301500 {
1501 AccountPolicyIface::accountUnlockTimeout(0);
1502 }
1503 else
1504 {
1505 uint32_t value32 = 0;
1506 try
1507 {
1508 tmp = std::stoul(valueStr, nullptr);
1509 if (tmp > std::numeric_limits<decltype(value32)>::max())
1510 {
1511 throw std::out_of_range("Out of range");
1512 }
1513 value32 = static_cast<decltype(value32)>(tmp);
1514 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001515 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301516 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001517 lg2::error("Exception for AccountUnlockTimeout: {ERR}", "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001518 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301519 }
1520 AccountPolicyIface::accountUnlockTimeout(value32);
1521 }
Nan Zhou4bc69812022-10-25 00:07:13 +00001522}
1523
1524void UserMgr::initUserObjects(void)
1525{
1526 // All user management lock has to be based on /etc/shadow
1527 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1528 std::vector<std::string> userNameList;
1529 std::vector<std::string> sshGrpUsersList;
1530 UserSSHLists userSSHLists = getUserAndSshGrpList();
1531 userNameList = std::move(userSSHLists.first);
1532 sshGrpUsersList = std::move(userSSHLists.second);
1533
1534 if (!userNameList.empty())
1535 {
1536 std::map<std::string, std::vector<std::string>> groupLists;
Nan Zhouda401fe2022-10-25 00:07:18 +00001537 // We only track users that are in the |predefinedGroups|
1538 // The other groups don't contain real BMC users.
1539 for (const char* grp : predefinedGroups)
Nan Zhou4bc69812022-10-25 00:07:13 +00001540 {
1541 if (grp == grpSsh)
1542 {
1543 groupLists.emplace(grp, sshGrpUsersList);
1544 }
1545 else
1546 {
1547 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1548 groupLists.emplace(grp, grpUsersList);
1549 }
1550 }
1551 for (auto& grp : privMgr)
1552 {
1553 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1554 groupLists.emplace(grp, grpUsersList);
1555 }
1556
1557 for (auto& user : userNameList)
1558 {
1559 std::vector<std::string> userGroups;
1560 std::string userPriv;
1561 for (const auto& grp : groupLists)
1562 {
1563 std::vector<std::string> tempGrp = grp.second;
1564 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1565 tempGrp.end())
1566 {
1567 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1568 privMgr.end())
1569 {
1570 userPriv = grp.first;
1571 }
1572 else
1573 {
1574 userGroups.emplace_back(grp.first);
1575 }
1576 }
1577 }
1578 // Add user objects to the Users path.
1579 sdbusplus::message::object_path tempObjPath(usersObjPath);
1580 tempObjPath /= user;
1581 std::string objPath(tempObjPath);
1582 std::sort(userGroups.begin(), userGroups.end());
1583 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1584 bus, objPath.c_str(), userGroups,
1585 userPriv, isUserEnabled(user), *this));
1586 }
1587 }
1588}
1589
1590UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1591 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1592 pamPasswdConfigFile(defaultPamPasswdConfigFile),
Jason M. Bills2d042d12023-03-28 15:32:45 -07001593 faillockConfigFile(defaultFaillockConfigFile),
1594 pwQualityConfigFile(defaultPWQualityConfigFile)
Nan Zhou4bc69812022-10-25 00:07:13 +00001595{
1596 UserMgrIface::allPrivileges(privMgr);
Nan Zhouda401fe2022-10-25 00:07:18 +00001597 groupsMgr = readAllGroupsOnSystem();
Nan Zhou4bc69812022-10-25 00:07:13 +00001598 std::sort(groupsMgr.begin(), groupsMgr.end());
1599 UserMgrIface::allGroups(groupsMgr);
1600 initializeAccountPolicy();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301601 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301602
1603 // emit the signal
1604 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301605}
1606
Nan Zhou49c81362022-10-25 00:07:08 +00001607void UserMgr::executeUserAdd(const char* userName, const char* groups,
1608 bool sshRequested, bool enabled)
1609{
1610 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1611 // 1970-01-01, that's an implementation-defined behavior
1612 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001613 (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
Nan Zhou49c81362022-10-25 00:07:08 +00001614 (enabled ? "" : "1970-01-01"));
1615}
1616
1617void UserMgr::executeUserDelete(const char* userName)
1618{
1619 executeCmd("/usr/sbin/userdel", userName, "-r");
1620}
1621
Jayanth Othayothac921a52023-07-21 03:48:55 -05001622void UserMgr::executeUserClearFailRecords(const char* userName)
1623{
1624 executeCmd("/usr/sbin/faillock", "--user", userName, "--reset");
1625}
1626
Nan Zhouf25443e2022-10-25 00:07:11 +00001627void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1628{
1629 std::string newHomeDir = "/home/";
1630 newHomeDir += newUserName;
1631 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1632 newHomeDir.c_str(), "-m");
1633}
1634
Nan Zhoufef63032022-10-25 00:07:12 +00001635void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1636 bool sshRequested)
1637{
1638 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001639 (sshRequested ? "/bin/sh" : "/sbin/nologin"));
Nan Zhoufef63032022-10-25 00:07:12 +00001640}
1641
Nan Zhou6b6f2d82022-10-25 00:07:17 +00001642void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1643{
1644 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1645 // 1970-01-01, that's an implementation-defined behavior
1646 executeCmd("/usr/sbin/usermod", userName, "-e",
1647 (enabled ? "" : "1970-01-01"));
1648}
1649
Nan Zhoua2953032022-11-11 21:50:32 +00001650std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1651{
Jason M. Bills2d042d12023-03-28 15:32:45 -07001652 return executeCmd("/usr/sbin/faillock", "--user", userName);
Nan Zhoua2953032022-11-11 21:50:32 +00001653}
1654
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301655} // namespace user
1656} // namespace phosphor