blob: 29c19f5474c0be7bd29dd373cf8a82aa5107ba55 [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* minPasswdLenProp = "minlen";
65static constexpr const char* remOldPasswdCount = "remember";
66static constexpr const char* maxFailedAttempt = "deny";
67static constexpr const char* unlockTimeout = "unlock_time";
Jason M. Bills2d042d12023-03-28 15:32:45 -070068static constexpr const char* defaultFaillockConfigFile =
69 "/etc/security/faillock.conf";
Jason M. Bills3b280ec2023-08-15 16:15:48 -070070static constexpr const char* defaultPWHistoryConfigFile =
71 "/etc/security/pwhistory.conf";
Jason M. Bills2d042d12023-03-28 15:32:45 -070072static constexpr const char* defaultPWQualityConfigFile =
73 "/etc/security/pwquality.conf";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053074
Ratan Guptaaeaf9412019-02-11 04:41:52 -060075// Object Manager related
Patrick Williams9638afb2021-02-22 17:16:24 -060076static constexpr const char* ldapMgrObjBasePath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060077 "/xyz/openbmc_project/user/ldap";
78
79// Object Mapper related
Patrick Williams9638afb2021-02-22 17:16:24 -060080static constexpr const char* objMapperService =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060081 "xyz.openbmc_project.ObjectMapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060082static constexpr const char* objMapperPath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060083 "/xyz/openbmc_project/object_mapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060084static constexpr const char* objMapperInterface =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060085 "xyz.openbmc_project.ObjectMapper";
86
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053087using namespace phosphor::logging;
88using InsufficientPermission =
89 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
90using InternalFailure =
91 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
92using InvalidArgument =
93 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
94using UserNameExists =
95 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
96using UserNameDoesNotExist =
97 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
98using UserNameGroupFail =
99 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530100using NoResource =
101 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530102using Argument = xyz::openbmc_project::Common::InvalidArgument;
Nan Zhouda401fe2022-10-25 00:07:18 +0000103using GroupNameExists =
104 sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameExists;
105using GroupNameDoesNotExists =
106 sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameDoesNotExist;
107
108namespace
109{
110
111// The hardcoded groups in OpenBMC projects
Patrick Williams16c2b682024-08-16 15:20:56 -0400112constexpr std::array<const char*, 4> predefinedGroups = {
113 "redfish", "ipmi", "ssh", "hostconsole"};
Nan Zhouda401fe2022-10-25 00:07:18 +0000114
115// These prefixes are for Dynamic Redfish authorization. See
116// https://github.com/openbmc/docs/blob/master/designs/redfish-authorization.md
117
118// Base role and base privileges are added by Redfish implementation (e.g.,
119// BMCWeb) at compile time
120constexpr std::array<const char*, 4> allowedGroupPrefix = {
121 "openbmc_rfr_", // OpenBMC Redfish Base Role
122 "openbmc_rfp_", // OpenBMC Redfish Base Privileges
123 "openbmc_orfr_", // OpenBMC Redfish OEM Role
124 "openbmc_orfp_", // OpenBMC Redfish OEM Privileges
125};
126
127void checkAndThrowsForGroupChangeAllowed(const std::string& groupName)
128{
129 bool allowed = false;
130 for (std::string_view prefix : allowedGroupPrefix)
131 {
132 if (groupName.starts_with(prefix))
133 {
134 allowed = true;
135 break;
136 }
137 }
138 if (!allowed)
139 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800140 lg2::error("Group name '{GROUP}' is not in the allowed list", "GROUP",
141 groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000142 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
143 Argument::ARGUMENT_VALUE(groupName.c_str()));
144 }
145}
146
147} // namespace
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530148
Nan Zhoue47c09d2022-10-25 00:06:41 +0000149std::string getCSVFromVector(std::span<const std::string> vec)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530150{
Nan Zhoue47c09d2022-10-25 00:06:41 +0000151 if (vec.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530152 {
Nan Zhoue47c09d2022-10-25 00:06:41 +0000153 return "";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530154 }
Nan Zhoue47c09d2022-10-25 00:06:41 +0000155 return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
156 [](std::string&& val, std::string_view element) {
Patrick Williams16c2b682024-08-16 15:20:56 -0400157 val += ',';
158 val += element;
159 return val;
160 });
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530161}
162
Nan Zhou332fb9d2022-10-25 00:07:03 +0000163bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530164{
165 std::string::size_type delStrPos = csvStr.find(delStr);
166 if (delStrPos != std::string::npos)
167 {
168 // need to also delete the comma char
169 if (delStrPos == 0)
170 {
171 csvStr.erase(delStrPos, delStr.size() + 1);
172 }
173 else
174 {
175 csvStr.erase(delStrPos - 1, delStr.size() + 1);
176 }
177 return true;
178 }
179 return false;
180}
181
Patrick Williams9638afb2021-02-22 17:16:24 -0600182bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530183{
184 if (userName.empty())
185 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800186 lg2::error("User name is empty");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530187 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
188 Argument::ARGUMENT_VALUE("Null"));
189 }
190 if (usersList.find(userName) == usersList.end())
191 {
192 return false;
193 }
194 return true;
195}
196
Patrick Williams9638afb2021-02-22 17:16:24 -0600197void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530198{
Nan Zhou8a11d992022-10-25 00:07:06 +0000199 if (!isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530200 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800201 lg2::error("User '{USERNAME}' does not exist", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530202 elog<UserNameDoesNotExist>();
203 }
204}
205
Nan Zhouda401fe2022-10-25 00:07:18 +0000206void UserMgr::checkAndThrowForDisallowedGroupCreation(
207 const std::string& groupName)
208{
209 if (groupName.size() > maxSystemGroupNameLength ||
210 !std::regex_match(groupName.c_str(),
nichanghao.nchd9adc732024-01-17 20:24:17 +0800211 std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
Nan Zhouda401fe2022-10-25 00:07:18 +0000212 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800213 lg2::error("Invalid group name '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000214 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
215 Argument::ARGUMENT_VALUE(groupName.c_str()));
216 }
217 checkAndThrowsForGroupChangeAllowed(groupName);
218}
219
Patrick Williams9638afb2021-02-22 17:16:24 -0600220void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530221{
Nan Zhou8a11d992022-10-25 00:07:06 +0000222 if (isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530223 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800224 lg2::error("User '{USERNAME}' already exists", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530225 elog<UserNameExists>();
226 }
227}
228
229void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600230 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530231{
232 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
233 groupNames.end())
234 {
235 if (userName.length() > ipmiMaxUserNameLen)
236 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800237 lg2::error("User '{USERNAME}' exceeds IPMI username length limit "
238 "({LENGTH} > {LIMIT})",
239 "USERNAME", userName, "LENGTH", userName.length(),
240 "LIMIT", ipmiMaxUserNameLen);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530241 elog<UserNameGroupFail>(
242 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
243 "IPMI length"));
244 }
245 }
246 if (userName.length() > systemMaxUserNameLen)
247 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800248 lg2::error("User '{USERNAME}' exceeds system username length limit "
249 "({LENGTH} > {LIMIT})",
250 "USERNAME", userName, "LENGTH", userName.length(), "LIMIT",
251 systemMaxUserNameLen);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530252 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
253 Argument::ARGUMENT_VALUE("Invalid length"));
254 }
255 if (!std::regex_match(userName.c_str(),
nichanghao.nchd9adc732024-01-17 20:24:17 +0800256 std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530257 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800258 lg2::error("Invalid username '{USERNAME}'", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530259 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
260 Argument::ARGUMENT_VALUE("Invalid data"));
261 }
262}
263
Patrick Williams66addf22024-12-18 11:21:29 -0500264void
265 UserMgr::throwForMaxGrpUserCount(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530266{
267 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
268 groupNames.end())
269 {
270 if (getIpmiUsersCount() >= ipmiMaxUsers)
271 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800272 lg2::error("IPMI user limit reached");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530273 elog<NoResource>(
274 xyz::openbmc_project::User::Common::NoResource::REASON(
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800275 "IPMI user limit reached"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530276 }
277 }
278 else
279 {
280 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
281 (maxSystemUsers - ipmiMaxUsers))
282 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800283 lg2::error("Non-ipmi User limit reached");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530284 elog<NoResource>(
285 xyz::openbmc_project::User::Common::NoResource::REASON(
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800286 "Non-ipmi user limit reached"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530287 }
288 }
289 return;
290}
291
Patrick Williams9638afb2021-02-22 17:16:24 -0600292void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530293{
294 if (!priv.empty() &&
295 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
296 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800297 lg2::error("Invalid privilege '{PRIVILEGE}'", "PRIVILEGE", priv);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530298 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
299 Argument::ARGUMENT_VALUE(priv.c_str()));
300 }
301}
302
Patrick Williams9638afb2021-02-22 17:16:24 -0600303void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530304{
Patrick Williams9638afb2021-02-22 17:16:24 -0600305 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530306 {
307 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
308 groupsMgr.end())
309 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800310 lg2::error("Invalid Group Name '{GROUPNAME}'", "GROUPNAME", group);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530311 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
312 Argument::ARGUMENT_VALUE(group.c_str()));
313 }
314 }
315}
316
Nan Zhouda401fe2022-10-25 00:07:18 +0000317std::vector<std::string> UserMgr::readAllGroupsOnSystem()
318{
319 std::vector<std::string> allGroups = {predefinedGroups.begin(),
320 predefinedGroups.end()};
321 // rewinds to the beginning of the group database
322 setgrent();
323 struct group* gr = getgrent();
324 while (gr != nullptr)
325 {
326 std::string group(gr->gr_name);
327 for (std::string_view prefix : allowedGroupPrefix)
328 {
329 if (group.starts_with(prefix))
330 {
331 allGroups.push_back(gr->gr_name);
332 }
333 }
334 gr = getgrent();
335 }
336 // close the group database
337 endgrent();
338 return allGroups;
339}
340
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530341void UserMgr::createUser(std::string userName,
342 std::vector<std::string> groupNames, std::string priv,
343 bool enabled)
344{
345 throwForInvalidPrivilege(priv);
346 throwForInvalidGroups(groupNames);
347 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500348 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530349 throwForUserExists(userName);
350 throwForUserNameConstraints(userName, groupNames);
351 throwForMaxGrpUserCount(groupNames);
352
353 std::string groups = getCSVFromVector(groupNames);
354 bool sshRequested = removeStringFromCSV(groups, grpSsh);
355
356 // treat privilege as a group - This is to avoid using different file to
357 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530358 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530359 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530360 if (groups.size() != 0)
361 {
362 groups += ",";
363 }
364 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530365 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530366 try
367 {
Nan Zhou49c81362022-10-25 00:07:08 +0000368 executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530369 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600370 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530371 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800372 lg2::error("Unable to create new user '{USERNAME}'", "USERNAME",
373 userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530374 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
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800386 lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530387 return;
388}
389
390void UserMgr::deleteUser(std::string userName)
391{
392 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500393 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530394 throwForUserDoesNotExist(userName);
395 try
396 {
Jayanth Othayothac921a52023-07-21 03:48:55 -0500397 // Clear user fail records
398 executeUserClearFailRecords(userName.c_str());
Patrick Williams4b294622023-08-08 10:19:18 -0500399
400 executeUserDelete(userName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530401 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600402 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530403 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800404 lg2::error("Delete User '{USERNAME}' failed", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530405 elog<InternalFailure>();
406 }
407
408 usersList.erase(userName);
409
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800410 lg2::info("User '{USERNAME}' deleted successfully", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530411 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 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800419 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000420 elog<GroupNameDoesNotExists>();
421 }
422 checkAndThrowsForGroupChangeAllowed(groupName);
423}
424
425void UserMgr::deleteGroup(std::string groupName)
426{
427 checkDeleteGroupConstraints(groupName);
428 try
429 {
430 executeGroupDeletion(groupName.c_str());
431 }
432 catch (const InternalFailure& e)
433 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800434 lg2::error("Failed to delete group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000435 elog<InternalFailure>();
436 }
437
438 groupsMgr.erase(std::find(groupsMgr.begin(), groupsMgr.end(), groupName));
439 UserMgrIface::allGroups(groupsMgr);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800440 lg2::info("Successfully deleted group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000441}
442
443void UserMgr::checkCreateGroupConstraints(const std::string& groupName)
444{
445 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) !=
446 groupsMgr.end())
447 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800448 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000449 elog<GroupNameExists>();
450 }
451 checkAndThrowForDisallowedGroupCreation(groupName);
452 if (groupsMgr.size() >= maxSystemGroupCount)
453 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800454 lg2::error("Group limit reached");
Nan Zhouda401fe2022-10-25 00:07:18 +0000455 elog<NoResource>(xyz::openbmc_project::User::Common::NoResource::REASON(
456 "Group limit reached"));
457 }
458}
459
460void UserMgr::createGroup(std::string groupName)
461{
462 checkCreateGroupConstraints(groupName);
463 try
464 {
465 executeGroupCreation(groupName.c_str());
466 }
467 catch (const InternalFailure& e)
468 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800469 lg2::error("Failed to create group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000470 elog<InternalFailure>();
471 }
472 groupsMgr.push_back(groupName);
473 UserMgrIface::allGroups(groupsMgr);
474}
475
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530476void UserMgr::renameUser(std::string userName, std::string newUserName)
477{
478 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500479 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530480 throwForUserDoesNotExist(userName);
481 throwForUserExists(newUserName);
482 throwForUserNameConstraints(newUserName,
483 usersList[userName].get()->userGroups());
484 try
485 {
Nan Zhouf25443e2022-10-25 00:07:11 +0000486 executeUserRename(userName.c_str(), newUserName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530487 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600488 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530489 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800490 lg2::error("Rename '{USERNAME}' to '{NEWUSERNAME}' failed", "USERNAME",
491 userName, "NEWUSERNAME", newUserName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530492 elog<InternalFailure>();
493 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600494 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530495 std::string priv = user.get()->userPrivilege();
496 std::vector<std::string> groupNames = user.get()->userGroups();
497 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530498 sdbusplus::message::object_path tempObjPath(usersObjPath);
499 tempObjPath /= newUserName;
500 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530501 // Special group 'ipmi' needs a way to identify user renamed, in order to
502 // update encrypted password. It can't rely only on InterfacesRemoved &
503 // InterfacesAdded. So first send out userRenamed signal.
504 this->userRenamed(userName, newUserName);
505 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000506 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
507 bus, newUserObj.c_str(), groupNames,
508 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530509 return;
510}
511
Patrick Williams9638afb2021-02-22 17:16:24 -0600512void UserMgr::updateGroupsAndPriv(const std::string& userName,
Nan Zhoufef63032022-10-25 00:07:12 +0000513 std::vector<std::string> groupNames,
Patrick Williams9638afb2021-02-22 17:16:24 -0600514 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530515{
516 throwForInvalidPrivilege(priv);
517 throwForInvalidGroups(groupNames);
518 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500519 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530520 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600521 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530522 usersList[userName].get()->userGroups();
523 std::vector<std::string> groupDiff;
524 // Note: already dealing with sorted group lists.
525 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
526 groupNames.begin(), groupNames.end(),
527 std::back_inserter(groupDiff));
528 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
529 groupDiff.end())
530 {
531 throwForUserNameConstraints(userName, groupNames);
532 throwForMaxGrpUserCount(groupNames);
533 }
534
535 std::string groups = getCSVFromVector(groupNames);
536 bool sshRequested = removeStringFromCSV(groups, grpSsh);
537
538 // treat privilege as a group - This is to avoid using different file to
539 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530540 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530541 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530542 if (groups.size() != 0)
543 {
544 groups += ",";
545 }
546 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530547 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530548 try
549 {
Nan Zhoufef63032022-10-25 00:07:12 +0000550 executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530551 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600552 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530553 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800554 lg2::error(
555 "Unable to modify user privilege / groups for user '{USERNAME}'",
556 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530557 elog<InternalFailure>();
558 }
559
Nan Zhoufef63032022-10-25 00:07:12 +0000560 std::sort(groupNames.begin(), groupNames.end());
561 usersList[userName]->setUserGroups(groupNames);
562 usersList[userName]->setUserPrivilege(priv);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800563 lg2::info("User '{USERNAME}' groups / privilege updated successfully",
564 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530565}
566
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530567uint8_t UserMgr::minPasswordLength(uint8_t value)
568{
569 if (value == AccountPolicyIface::minPasswordLength())
570 {
571 return value;
572 }
573 if (value < minPasswdLength)
574 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800575 lg2::error("Attempting to set minPasswordLength to {VALUE}, less than "
576 "{MINVALUE}",
577 "VALUE", value, "MINVALUE", minPasswdLength);
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000578 elog<InvalidArgument>(
579 Argument::ARGUMENT_NAME("minPasswordLength"),
580 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530581 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700582 if (setPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
583 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530584 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800585 lg2::error("Unable to set minPasswordLength to {VALUE}", "VALUE",
586 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530587 elog<InternalFailure>();
588 }
589 return AccountPolicyIface::minPasswordLength(value);
590}
591
592uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
593{
594 if (value == AccountPolicyIface::rememberOldPasswordTimes())
595 {
596 return value;
597 }
Jason M. Bills3b280ec2023-08-15 16:15:48 -0700598 if (setPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
599 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530600 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800601 lg2::error("Unable to set rememberOldPasswordTimes to {VALUE}", "VALUE",
602 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530603 elog<InternalFailure>();
604 }
605 return AccountPolicyIface::rememberOldPasswordTimes(value);
606}
607
608uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
609{
610 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
611 {
612 return value;
613 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700614 if (setPamModuleConfValue(faillockConfigFile, maxFailedAttempt,
615 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530616 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800617 lg2::error("Unable to set maxLoginAttemptBeforeLockout to {VALUE}",
618 "VALUE", value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530619 elog<InternalFailure>();
620 }
621 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
622}
623
624uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
625{
626 if (value == AccountPolicyIface::accountUnlockTimeout())
627 {
628 return value;
629 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700630 if (setPamModuleConfValue(faillockConfigFile, unlockTimeout,
631 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530632 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800633 lg2::error("Unable to set accountUnlockTimeout to {VALUE}", "VALUE",
634 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530635 elog<InternalFailure>();
636 }
637 return AccountPolicyIface::accountUnlockTimeout(value);
638}
639
Jason M. Bills2d042d12023-03-28 15:32:45 -0700640int UserMgr::getPamModuleConfValue(const std::string& confFile,
641 const std::string& argName,
642 std::string& argValue)
643{
644 std::ifstream fileToRead(confFile, std::ios::in);
645 if (!fileToRead.is_open())
646 {
647 lg2::error("Failed to open pam configuration file {FILENAME}",
648 "FILENAME", confFile);
649 return failure;
650 }
651 std::string line;
652 auto argSearch = argName + "=";
653 size_t startPos = 0;
654 size_t endPos = 0;
655 while (getline(fileToRead, line))
656 {
657 // skip comments section starting with #
658 if ((startPos = line.find('#')) != std::string::npos)
659 {
660 if (startPos == 0)
661 {
662 continue;
663 }
664 // skip comments after meaningful section and process those
665 line = line.substr(0, startPos);
666 }
667 if ((startPos = line.find(argSearch)) != std::string::npos)
668 {
669 if ((endPos = line.find(' ', startPos)) == std::string::npos)
670 {
671 endPos = line.size();
672 }
673 startPos += argSearch.size();
674 argValue = line.substr(startPos, endPos - startPos);
675 return success;
676 }
677 }
678 return failure;
679}
680
Jason M. Bills2d042d12023-03-28 15:32:45 -0700681int UserMgr::setPamModuleConfValue(const std::string& confFile,
682 const std::string& argName,
683 const std::string& argValue)
684{
685 std::string tmpConfFile = confFile + "_tmp";
686 std::ifstream fileToRead(confFile, std::ios::in);
687 std::ofstream fileToWrite(tmpConfFile, std::ios::out);
688 if (!fileToRead.is_open() || !fileToWrite.is_open())
689 {
690 lg2::error("Failed to open pam configuration file {FILENAME}",
691 "FILENAME", confFile);
Jason M. Bills17b88272023-05-22 14:24:02 -0700692 // Delete the unused tmp file
693 std::remove(tmpConfFile.c_str());
Jason M. Bills2d042d12023-03-28 15:32:45 -0700694 return failure;
695 }
696 std::string line;
697 auto argSearch = argName + "=";
698 size_t startPos = 0;
699 size_t endPos = 0;
700 bool found = false;
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 fileToWrite << line << std::endl;
709 continue;
710 }
711 // skip comments after meaningful section and process those
712 line = line.substr(0, startPos);
713 }
714 if ((startPos = line.find(argSearch)) != std::string::npos)
715 {
716 if ((endPos = line.find(' ', startPos)) == std::string::npos)
717 {
718 endPos = line.size();
719 }
720 startPos += argSearch.size();
721 fileToWrite << line.substr(0, startPos) << argValue
722 << line.substr(endPos, line.size() - endPos)
723 << std::endl;
724 found = true;
725 continue;
726 }
727 fileToWrite << line << std::endl;
728 }
729 fileToWrite.close();
730 fileToRead.close();
731 if (found)
732 {
733 if (std::rename(tmpConfFile.c_str(), confFile.c_str()) == 0)
734 {
735 return success;
736 }
737 }
Jason M. Bills17b88272023-05-22 14:24:02 -0700738 // No changes, so delete the unused tmp file
739 std::remove(tmpConfFile.c_str());
Jason M. Bills2d042d12023-03-28 15:32:45 -0700740 return failure;
741}
742
Patrick Williams9638afb2021-02-22 17:16:24 -0600743void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530744{
745 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500746 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530747 throwForUserDoesNotExist(userName);
748 try
749 {
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000750 executeUserModifyUserEnable(userName.c_str(), enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530751 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600752 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530753 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800754 lg2::error("Unable to modify user enabled state for '{USERNAME}'",
755 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530756 elog<InternalFailure>();
757 }
758
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000759 usersList[userName]->setUserEnabled(enabled);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800760 lg2::info("User '{USERNAME}' has been {STATUS}", "USERNAME", userName,
761 "STATUS", enabled ? "Enabled" : "Disabled");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530762}
763
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530764/**
Jason M. Bills2d042d12023-03-28 15:32:45 -0700765 * faillock app will provide the user failed login list with when the attempt
766 * was made, the type, the source, and if it's valid.
767 *
768 * Valid in this case means that the attempt was made within the fail_interval
769 * time. So, we can check this list for the number of valid entries (lines
770 * ending with 'V') compared to the maximum allowed to determine if the user is
771 * locked out.
772 *
773 * This data is only refreshed when an attempt is made, so if the user appears
774 * to be locked out, we must also check if the most recent attempt was older
775 * than the unlock_time to know if the user has since been unlocked.
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530776 **/
Jason M. Bills2d042d12023-03-28 15:32:45 -0700777bool UserMgr::parseFaillockForLockout(
778 const std::vector<std::string>& faillockOutput)
779{
780 uint16_t failAttempts = 0;
781 time_t lastFailedAttempt{};
782 for (const std::string& line : faillockOutput)
783 {
784 if (!line.ends_with("V"))
785 {
786 continue;
787 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530788
Jason M. Bills2d042d12023-03-28 15:32:45 -0700789 // Count this failed attempt
790 failAttempts++;
791
792 // Update the last attempt time
793 // First get the "when" which is the first two words (date and time)
794 size_t pos = line.find(" ");
795 if (pos == std::string::npos)
796 {
797 continue;
798 }
799 pos = line.find(" ", pos + 1);
800 if (pos == std::string::npos)
801 {
802 continue;
803 }
804 std::string failDateTime = line.substr(0, pos);
805
806 // NOTE: Cannot use std::get_time() here as the implementation of %y in
807 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
808 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
809 std::tm tmStruct = {};
810 if (!strptime(failDateTime.c_str(), "%F %T", &tmStruct))
811 {
812 lg2::error("Failed to parse latest failure date/time");
813 elog<InternalFailure>();
814 }
815
816 time_t failTimestamp = std::mktime(&tmStruct);
817 lastFailedAttempt = std::max(failTimestamp, lastFailedAttempt);
818 }
819
820 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
821 {
822 return false;
823 }
824
825 if (lastFailedAttempt +
826 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
827 std::time(NULL))
828 {
829 return false;
830 }
831
832 return true;
833}
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530834
Patrick Williams9638afb2021-02-22 17:16:24 -0600835bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530836{
837 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500838 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800839 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
840 {
841 return false;
842 }
843
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530844 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800845 try
846 {
Nan Zhoua2953032022-11-11 21:50:32 +0000847 output = getFailedAttempt(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800848 }
849 catch (const InternalFailure& e)
850 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800851 lg2::error("Unable to read login failure counter");
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800852 elog<InternalFailure>();
853 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530854
Jason M. Bills2d042d12023-03-28 15:32:45 -0700855 return parseFaillockForLockout(output);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530856}
857
Patrick Williams9638afb2021-02-22 17:16:24 -0600858bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
859 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530860{
861 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500862 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530863 if (value == true)
864 {
865 return userLockedForFailedAttempt(userName);
866 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530867
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800868 try
869 {
Jayanth Othayothac921a52023-07-21 03:48:55 -0500870 // Clear user fail records
871 executeUserClearFailRecords(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800872 }
873 catch (const InternalFailure& e)
874 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800875 lg2::error("Unable to reset login failure counter");
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800876 elog<InternalFailure>();
877 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530878
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530879 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530880}
881
Patrick Williams9638afb2021-02-22 17:16:24 -0600882bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600883{
884 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500885 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600886
Patrick Williams66addf22024-12-18 11:21:29 -0500887 struct spwd spwd{};
Patrick Williams9638afb2021-02-22 17:16:24 -0600888 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600889 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
George Liu34e6ccd2024-09-18 09:22:53 +0800890 if (buflen <= 0)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600891 {
892 // Use a default size if there is no hard limit suggested by sysconf()
893 buflen = 1024;
894 }
895 std::vector<char> buffer(buflen);
Patrick Williams16c2b682024-08-16 15:20:56 -0400896 auto status =
897 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600898 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
899 // If no matching password record was found, these functions return 0
900 // and store NULL in *spwdPtr
901 if ((status == 0) && (&spwd == spwdPtr))
902 {
903 // Determine password validity per "chage" docs, where:
904 // spwd.sp_lstchg == 0 means password is expired, and
905 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000906 constexpr long secondsPerDay = 60 * 60 * 24;
907 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600908 if ((spwd.sp_lstchg == 0) ||
909 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
910 {
911 return true;
912 }
913 }
914 else
915 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000916 // User entry is missing in /etc/shadow, indicating no SHA password.
917 // Treat this as new user without password entry in /etc/shadow
918 // TODO: Add property to indicate user password was not set yet
919 // https://github.com/openbmc/phosphor-user-manager/issues/8
920 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600921 }
922
923 return false;
924}
925
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530926UserSSHLists UserMgr::getUserAndSshGrpList()
927{
928 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500929 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530930
931 std::vector<std::string> userList;
932 std::vector<std::string> sshUsersList;
933 struct passwd pw, *pwp = nullptr;
934 std::array<char, 1024> buffer{};
935
936 phosphor::user::File passwd(passwdFileName, "r");
937 if ((passwd)() == NULL)
938 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800939 lg2::error("Error opening {FILENAME}", "FILENAME", passwdFileName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530940 elog<InternalFailure>();
941 }
942
943 while (true)
944 {
945 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
946 &pwp);
947 if ((r != 0) || (pwp == NULL))
948 {
949 // Any error, break the loop.
950 break;
951 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530952#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530953 // Add all users whose UID >= 1000 and < 65534
954 // and special UID 0.
955 if ((pwp->pw_uid == 0) ||
956 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530957#else
958 // Add all users whose UID >=1000 and < 65534
959 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
960#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530961 {
962 std::string userName(pwp->pw_name);
963 userList.emplace_back(userName);
964
965 // ssh doesn't have separate group. Check login shell entry to
966 // get all users list which are member of ssh group.
967 std::string loginShell(pwp->pw_shell);
968 if (loginShell == "/bin/sh")
969 {
970 sshUsersList.emplace_back(userName);
971 }
972 }
973 }
974 endpwent();
975 return std::make_pair(std::move(userList), std::move(sshUsersList));
976}
977
978size_t UserMgr::getIpmiUsersCount()
979{
980 std::vector<std::string> userList = getUsersInGroup("ipmi");
981 return userList.size();
982}
983
Nan Zhou49c81362022-10-25 00:07:08 +0000984size_t UserMgr::getNonIpmiUsersCount()
985{
986 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
987 return usersList.size() - ipmiUsers.size();
988}
989
Patrick Williams9638afb2021-02-22 17:16:24 -0600990bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530991{
992 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500993 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530994 std::array<char, 4096> buffer{};
995 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600996 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530997 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
998 buffer.max_size(), &resultPtr);
999 if (!status && (&spwd == resultPtr))
1000 {
1001 if (resultPtr->sp_expire >= 0)
1002 {
1003 return false; // user locked out
1004 }
1005 return true;
1006 }
1007 return false; // assume user is disabled for any error.
1008}
1009
Patrick Williams9638afb2021-02-22 17:16:24 -06001010std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301011{
1012 std::vector<std::string> usersInGroup;
1013 // Should be more than enough to get the pwd structure.
1014 std::array<char, 4096> buffer{};
1015 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -06001016 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301017
1018 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1019 buffer.max_size(), &resultPtr);
1020
1021 if (!status && (&grp == resultPtr))
1022 {
1023 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
1024 {
1025 usersInGroup.emplace_back(*(grp.gr_mem));
1026 }
1027 }
1028 else
1029 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001030 lg2::error("Group '{GROUPNAME}' not found", "GROUPNAME", groupName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301031 // Don't throw error, just return empty userList - fallback
1032 }
1033 return usersInGroup;
1034}
1035
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001036DbusUserObj UserMgr::getPrivilegeMapperObject(void)
1037{
1038 DbusUserObj objects;
1039 try
1040 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001041 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
1042 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001043
Patrick Williams16c2b682024-08-16 15:20:56 -04001044 auto ldapMgmtService =
1045 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001046 auto method = bus.new_method_call(
1047 ldapMgmtService.c_str(), ldapMgrObjBasePath,
1048 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1049
1050 auto reply = bus.call(method);
1051 reply.read(objects);
1052 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001053 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001054 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001055 lg2::error("Unable to get the User Service: {ERR}", "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001056 throw;
1057 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001058 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001059 {
Manojkiran Eda46e773a2024-06-17 14:45:33 +05301060 lg2::error("Failed to execute GetManagedObjects at {PATH}: {ERR}",
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001061 "PATH", ldapMgrObjBasePath, "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001062 throw;
1063 }
1064 return objects;
1065}
1066
Patrick Williams9638afb2021-02-22 17:16:24 -06001067std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001068{
1069 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1070 objMapperInterface, "GetObject");
1071
1072 mapperCall.append(std::move(path));
1073 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1074
1075 auto mapperResponseMsg = bus.call(mapperCall);
1076
1077 if (mapperResponseMsg.is_method_error())
1078 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001079 lg2::error("Error in mapper call");
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001080 elog<InternalFailure>();
1081 }
1082
1083 std::map<std::string, std::vector<std::string>> mapperResponse;
1084 mapperResponseMsg.read(mapperResponse);
1085
1086 if (mapperResponse.begin() == mapperResponse.end())
1087 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001088 lg2::error("Invalid response from mapper");
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001089 elog<InternalFailure>();
1090 }
1091
1092 return mapperResponse.begin()->first;
1093}
1094
Alexander Filippov75626582022-02-09 18:42:37 +03001095gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
1096{
1097 static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1098 if (buflen <= 0)
1099 {
1100 // Use a default size if there is no hard limit suggested by sysconf()
1101 buflen = 1024;
1102 }
1103
1104 struct passwd pwd;
1105 struct passwd* pwdPtr = nullptr;
1106 std::vector<char> buffer(buflen);
1107
1108 auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(),
1109 buffer.size(), &pwdPtr);
1110 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
1111 // If no matching password record was found, these functions return 0
1112 // and store NULL in *pwdPtr
1113 if (!status && (&pwd == pwdPtr))
1114 {
1115 return pwd.pw_gid;
1116 }
1117
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001118 lg2::error("User {USERNAME} does not exist", "USERNAME", userName);
Alexander Filippov75626582022-02-09 18:42:37 +03001119 elog<UserNameDoesNotExist>();
1120}
1121
1122bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
1123 const std::string& groupName) const
1124{
1125 static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1126 if (buflen <= 0)
1127 {
1128 // Use a default size if there is no hard limit suggested by sysconf()
1129 buflen = 1024;
1130 }
1131
1132 struct group grp;
1133 struct group* grpPtr = nullptr;
1134 std::vector<char> buffer(buflen);
1135
1136 auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1137 buffer.size(), &grpPtr);
1138
1139 // Groups with a lot of members may require a buffer of bigger size than
1140 // suggested by _SC_GETGR_R_SIZE_MAX.
1141 // 32K should be enough for about 2K members.
1142 constexpr auto maxBufferLength = 32 * 1024;
1143 while (status == ERANGE && buflen < maxBufferLength)
1144 {
1145 buflen *= 2;
1146 buffer.resize(buflen);
1147
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001148 lg2::debug("Increase buffer for getgrnam_r() to {SIZE}", "SIZE",
1149 buflen);
Alexander Filippov75626582022-02-09 18:42:37 +03001150
1151 status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1152 buffer.size(), &grpPtr);
1153 }
1154
1155 // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
1156 // If no matching group record was found, these functions return 0
1157 // and store NULL in *grpPtr
1158 if (!status && (&grp == grpPtr))
1159 {
1160 if (primaryGid == grp.gr_gid)
1161 {
1162 return true;
1163 }
1164
1165 for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
1166 {
1167 if (userName == grp.gr_mem[i])
1168 {
1169 return true;
1170 }
1171 }
1172 }
1173 else if (status == ERANGE)
1174 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001175 lg2::error("Group info of {GROUP} requires too much memory", "GROUP",
1176 groupName);
Alexander Filippov75626582022-02-09 18:42:37 +03001177 }
1178 else
1179 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001180 lg2::error("Group {GROUP} does not exist", "GROUP", groupName);
Alexander Filippov75626582022-02-09 18:42:37 +03001181 }
1182
1183 return false;
1184}
1185
Nan Zhouda401fe2022-10-25 00:07:18 +00001186void UserMgr::executeGroupCreation(const char* groupName)
1187{
1188 executeCmd("/usr/sbin/groupadd", groupName);
1189}
1190
1191void UserMgr::executeGroupDeletion(const char* groupName)
1192{
1193 executeCmd("/usr/sbin/groupdel", groupName);
1194}
1195
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001196UserInfoMap UserMgr::getUserInfo(std::string userName)
1197{
1198 UserInfoMap userInfo;
1199 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001200 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001201 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001202 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001203 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1204 userInfo.emplace("UserGroups", user.get()->userGroups());
1205 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1206 userInfo.emplace("UserLockedForFailedAttempt",
1207 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001208 userInfo.emplace("UserPasswordExpired",
1209 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001210 userInfo.emplace("RemoteUser", false);
1211 }
1212 else
1213 {
Alexander Filippov75626582022-02-09 18:42:37 +03001214 auto primaryGid = getPrimaryGroup(userName);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001215
1216 DbusUserObj objects = getPrivilegeMapperObject();
1217
Ravi Teja5fe724a2019-05-07 05:14:42 -05001218 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001219 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001220
1221 try
1222 {
Alexander Filippov75626582022-02-09 18:42:37 +03001223 for (const auto& [path, interfaces] : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001224 {
Alexander Filippov75626582022-02-09 18:42:37 +03001225 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
1226 if (it != interfaces.end())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001227 {
Alexander Filippov75626582022-02-09 18:42:37 +03001228 auto propIt = it->second.find("Enabled");
1229 if (propIt != it->second.end() &&
1230 std::get<bool>(propIt->second))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001231 {
Alexander Filippov75626582022-02-09 18:42:37 +03001232 ldapConfigPath = path.str + '/';
1233 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001234 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001235 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001236 }
1237
1238 if (ldapConfigPath.empty())
1239 {
1240 return userInfo;
1241 }
1242
Alexander Filippov75626582022-02-09 18:42:37 +03001243 for (const auto& [path, interfaces] : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001244 {
Alexander Filippov75626582022-02-09 18:42:37 +03001245 if (!path.str.starts_with(ldapConfigPath))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001246 {
Alexander Filippov75626582022-02-09 18:42:37 +03001247 continue;
1248 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001249
Alexander Filippov75626582022-02-09 18:42:37 +03001250 auto it = interfaces.find(
1251 "xyz.openbmc_project.User.PrivilegeMapperEntry");
1252 if (it != interfaces.end())
1253 {
1254 std::string privilege;
1255 std::string groupName;
1256
1257 for (const auto& [propName, propValue] : it->second)
1258 {
1259 if (propName == "GroupName")
Ravi Teja5fe724a2019-05-07 05:14:42 -05001260 {
Alexander Filippov75626582022-02-09 18:42:37 +03001261 groupName = std::get<std::string>(propValue);
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001262 }
Alexander Filippov75626582022-02-09 18:42:37 +03001263 else if (propName == "Privilege")
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001264 {
Alexander Filippov75626582022-02-09 18:42:37 +03001265 privilege = std::get<std::string>(propValue);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001266 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001267 }
Alexander Filippov75626582022-02-09 18:42:37 +03001268
1269 if (!groupName.empty() && !privilege.empty() &&
1270 isGroupMember(userName, primaryGid, groupName))
1271 {
1272 userPrivilege = privilege;
1273 break;
1274 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001275 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001276 if (!userPrivilege.empty())
1277 {
1278 break;
1279 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001280 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001281
Jiaqing Zhao56862062022-05-31 19:16:09 +08001282 if (!userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001283 {
Jiaqing Zhao56862062022-05-31 19:16:09 +08001284 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001285 }
Jiaqing Zhao56862062022-05-31 19:16:09 +08001286 else
1287 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001288 lg2::warning("LDAP group privilege mapping does not exist, "
1289 "default \"priv-user\" is used");
Jiaqing Zhao56862062022-05-31 19:16:09 +08001290 userInfo.emplace("UserPrivilege", "priv-user");
1291 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001292 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001293 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001294 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001295 lg2::error("Error while accessing variant: {ERR}", "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001296 elog<InternalFailure>();
1297 }
1298 userInfo.emplace("RemoteUser", true);
1299 }
1300
1301 return userInfo;
1302}
1303
Nan Zhou4bc69812022-10-25 00:07:13 +00001304void UserMgr::initializeAccountPolicy()
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301305{
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301306 std::string valueStr;
1307 auto value = minPasswdLength;
1308 unsigned long tmp = 0;
Jason M. Bills2d042d12023-03-28 15:32:45 -07001309 if (getPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
1310 valueStr) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301311 {
1312 AccountPolicyIface::minPasswordLength(minPasswdLength);
1313 }
1314 else
1315 {
1316 try
1317 {
1318 tmp = std::stoul(valueStr, nullptr);
1319 if (tmp > std::numeric_limits<decltype(value)>::max())
1320 {
1321 throw std::out_of_range("Out of range");
1322 }
1323 value = static_cast<decltype(value)>(tmp);
1324 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001325 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301326 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001327 lg2::error("Exception for MinPasswordLength: {ERR}", "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001328 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301329 }
1330 AccountPolicyIface::minPasswordLength(value);
1331 }
1332 valueStr.clear();
Jason M. Bills3b280ec2023-08-15 16:15:48 -07001333 if (getPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
1334 valueStr) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301335 {
1336 AccountPolicyIface::rememberOldPasswordTimes(0);
1337 }
1338 else
1339 {
1340 value = 0;
1341 try
1342 {
1343 tmp = std::stoul(valueStr, nullptr);
1344 if (tmp > std::numeric_limits<decltype(value)>::max())
1345 {
1346 throw std::out_of_range("Out of range");
1347 }
1348 value = static_cast<decltype(value)>(tmp);
1349 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001350 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301351 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001352 lg2::error("Exception for RememberOldPasswordTimes: {ERR}", "ERR",
1353 e);
Patrick Venture045b1122018-10-16 15:59:29 -07001354 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301355 }
1356 AccountPolicyIface::rememberOldPasswordTimes(value);
1357 }
1358 valueStr.clear();
Jason M. Bills2d042d12023-03-28 15:32:45 -07001359 if (getPamModuleConfValue(faillockConfigFile, maxFailedAttempt, valueStr) !=
1360 success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301361 {
1362 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1363 }
1364 else
1365 {
1366 uint16_t value16 = 0;
1367 try
1368 {
1369 tmp = std::stoul(valueStr, nullptr);
1370 if (tmp > std::numeric_limits<decltype(value16)>::max())
1371 {
1372 throw std::out_of_range("Out of range");
1373 }
1374 value16 = static_cast<decltype(value16)>(tmp);
1375 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001376 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301377 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001378 lg2::error("Exception for MaxLoginAttemptBeforLockout: {ERR}",
1379 "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001380 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301381 }
1382 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1383 }
1384 valueStr.clear();
Jason M. Bills2d042d12023-03-28 15:32:45 -07001385 if (getPamModuleConfValue(faillockConfigFile, unlockTimeout, valueStr) !=
1386 success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301387 {
1388 AccountPolicyIface::accountUnlockTimeout(0);
1389 }
1390 else
1391 {
1392 uint32_t value32 = 0;
1393 try
1394 {
1395 tmp = std::stoul(valueStr, nullptr);
1396 if (tmp > std::numeric_limits<decltype(value32)>::max())
1397 {
1398 throw std::out_of_range("Out of range");
1399 }
1400 value32 = static_cast<decltype(value32)>(tmp);
1401 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001402 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301403 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001404 lg2::error("Exception for AccountUnlockTimeout: {ERR}", "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001405 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301406 }
1407 AccountPolicyIface::accountUnlockTimeout(value32);
1408 }
Nan Zhou4bc69812022-10-25 00:07:13 +00001409}
1410
1411void UserMgr::initUserObjects(void)
1412{
1413 // All user management lock has to be based on /etc/shadow
1414 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1415 std::vector<std::string> userNameList;
1416 std::vector<std::string> sshGrpUsersList;
1417 UserSSHLists userSSHLists = getUserAndSshGrpList();
1418 userNameList = std::move(userSSHLists.first);
1419 sshGrpUsersList = std::move(userSSHLists.second);
1420
1421 if (!userNameList.empty())
1422 {
1423 std::map<std::string, std::vector<std::string>> groupLists;
Nan Zhouda401fe2022-10-25 00:07:18 +00001424 // We only track users that are in the |predefinedGroups|
1425 // The other groups don't contain real BMC users.
1426 for (const char* grp : predefinedGroups)
Nan Zhou4bc69812022-10-25 00:07:13 +00001427 {
1428 if (grp == grpSsh)
1429 {
1430 groupLists.emplace(grp, sshGrpUsersList);
1431 }
1432 else
1433 {
1434 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1435 groupLists.emplace(grp, grpUsersList);
1436 }
1437 }
1438 for (auto& grp : privMgr)
1439 {
1440 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1441 groupLists.emplace(grp, grpUsersList);
1442 }
1443
1444 for (auto& user : userNameList)
1445 {
1446 std::vector<std::string> userGroups;
1447 std::string userPriv;
1448 for (const auto& grp : groupLists)
1449 {
1450 std::vector<std::string> tempGrp = grp.second;
1451 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1452 tempGrp.end())
1453 {
1454 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1455 privMgr.end())
1456 {
1457 userPriv = grp.first;
1458 }
1459 else
1460 {
1461 userGroups.emplace_back(grp.first);
1462 }
1463 }
1464 }
1465 // Add user objects to the Users path.
1466 sdbusplus::message::object_path tempObjPath(usersObjPath);
1467 tempObjPath /= user;
1468 std::string objPath(tempObjPath);
1469 std::sort(userGroups.begin(), userGroups.end());
1470 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1471 bus, objPath.c_str(), userGroups,
1472 userPriv, isUserEnabled(user), *this));
1473 }
1474 }
1475}
1476
1477UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1478 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
Jason M. Bills2d042d12023-03-28 15:32:45 -07001479 faillockConfigFile(defaultFaillockConfigFile),
Jason M. Bills3b280ec2023-08-15 16:15:48 -07001480 pwHistoryConfigFile(defaultPWHistoryConfigFile),
Jason M. Bills2d042d12023-03-28 15:32:45 -07001481 pwQualityConfigFile(defaultPWQualityConfigFile)
Nan Zhou4bc69812022-10-25 00:07:13 +00001482{
1483 UserMgrIface::allPrivileges(privMgr);
Nan Zhouda401fe2022-10-25 00:07:18 +00001484 groupsMgr = readAllGroupsOnSystem();
Nan Zhou4bc69812022-10-25 00:07:13 +00001485 std::sort(groupsMgr.begin(), groupsMgr.end());
1486 UserMgrIface::allGroups(groupsMgr);
1487 initializeAccountPolicy();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301488 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301489
1490 // emit the signal
1491 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301492}
1493
Nan Zhou49c81362022-10-25 00:07:08 +00001494void UserMgr::executeUserAdd(const char* userName, const char* groups,
1495 bool sshRequested, bool enabled)
1496{
1497 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1498 // 1970-01-01, that's an implementation-defined behavior
1499 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001500 (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
Nan Zhou49c81362022-10-25 00:07:08 +00001501 (enabled ? "" : "1970-01-01"));
1502}
1503
1504void UserMgr::executeUserDelete(const char* userName)
1505{
1506 executeCmd("/usr/sbin/userdel", userName, "-r");
1507}
1508
Jayanth Othayothac921a52023-07-21 03:48:55 -05001509void UserMgr::executeUserClearFailRecords(const char* userName)
1510{
1511 executeCmd("/usr/sbin/faillock", "--user", userName, "--reset");
1512}
1513
Nan Zhouf25443e2022-10-25 00:07:11 +00001514void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1515{
1516 std::string newHomeDir = "/home/";
1517 newHomeDir += newUserName;
1518 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1519 newHomeDir.c_str(), "-m");
1520}
1521
Nan Zhoufef63032022-10-25 00:07:12 +00001522void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1523 bool sshRequested)
1524{
1525 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001526 (sshRequested ? "/bin/sh" : "/sbin/nologin"));
Nan Zhoufef63032022-10-25 00:07:12 +00001527}
1528
Nan Zhou6b6f2d82022-10-25 00:07:17 +00001529void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1530{
1531 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1532 // 1970-01-01, that's an implementation-defined behavior
1533 executeCmd("/usr/sbin/usermod", userName, "-e",
1534 (enabled ? "" : "1970-01-01"));
1535}
1536
Nan Zhoua2953032022-11-11 21:50:32 +00001537std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1538{
Jason M. Bills2d042d12023-03-28 15:32:45 -07001539 return executeCmd("/usr/sbin/faillock", "--user", userName);
Nan Zhoua2953032022-11-11 21:50:32 +00001540}
1541
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301542} // namespace user
1543} // namespace phosphor