blob: 2aa15e8b24332b51ebb706be63f2c0bcf3a828cd [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>
Abhilash Raju93804eb2024-10-01 00:24:43 -050043#include <filesystem>
Patrick Williams9638afb2021-02-22 17:16:24 -060044#include <fstream>
45#include <numeric>
46#include <regex>
Nan Zhoue47c09d2022-10-25 00:06:41 +000047#include <span>
48#include <string>
49#include <string_view>
50#include <vector>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053051namespace 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;
Malik Akbar Hashemi Rafsanjani27d56762025-03-12 03:19:52 -070058static constexpr size_t systemMaxUserNameLen = 100;
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
Chandramohan Harkude9ca86922025-05-18 13:09:25 +053063uint8_t maxPasswdLength = MAX_PASSWORD_LENGTH;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +053064// pam modules related
Patrick Williams9638afb2021-02-22 17:16:24 -060065static constexpr const char* minPasswdLenProp = "minlen";
66static constexpr const char* remOldPasswdCount = "remember";
67static constexpr const char* maxFailedAttempt = "deny";
68static constexpr const char* unlockTimeout = "unlock_time";
Jason M. Bills2d042d12023-03-28 15:32:45 -070069static constexpr const char* defaultFaillockConfigFile =
70 "/etc/security/faillock.conf";
Jason M. Bills3b280ec2023-08-15 16:15:48 -070071static constexpr const char* defaultPWHistoryConfigFile =
72 "/etc/security/pwhistory.conf";
Jason M. Bills2d042d12023-03-28 15:32:45 -070073static 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{
Abhilash Raju93804eb2024-10-01 00:24:43 -0500111constexpr auto mfaConfPath = "/var/lib/usr_mgr.conf";
Nan Zhouda401fe2022-10-25 00:07:18 +0000112// The hardcoded groups in OpenBMC projects
Patrick Williams16c2b682024-08-16 15:20:56 -0400113constexpr std::array<const char*, 4> predefinedGroups = {
114 "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
Ivan Moiseevc4183b82024-12-25 11:59:49 +0300148long currentDate()
149{
150 const auto date = std::chrono::duration_cast<std::chrono::days>(
151 std::chrono::system_clock::now().time_since_epoch())
152 .count();
153
154 if (date > std::numeric_limits<long>::max())
155 {
156 return std::numeric_limits<long>::max();
157 }
158
159 if (date < std::numeric_limits<long>::min())
160 {
161 return std::numeric_limits<long>::min();
162 }
163
164 return date;
165}
166
Nan Zhouda401fe2022-10-25 00:07:18 +0000167} // namespace
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530168
Nan Zhoue47c09d2022-10-25 00:06:41 +0000169std::string getCSVFromVector(std::span<const std::string> vec)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530170{
Nan Zhoue47c09d2022-10-25 00:06:41 +0000171 if (vec.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530172 {
Nan Zhoue47c09d2022-10-25 00:06:41 +0000173 return "";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530174 }
Nan Zhoue47c09d2022-10-25 00:06:41 +0000175 return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
176 [](std::string&& val, std::string_view element) {
Patrick Williams16c2b682024-08-16 15:20:56 -0400177 val += ',';
178 val += element;
179 return val;
180 });
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530181}
182
Nan Zhou332fb9d2022-10-25 00:07:03 +0000183bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530184{
185 std::string::size_type delStrPos = csvStr.find(delStr);
186 if (delStrPos != std::string::npos)
187 {
188 // need to also delete the comma char
189 if (delStrPos == 0)
190 {
191 csvStr.erase(delStrPos, delStr.size() + 1);
192 }
193 else
194 {
195 csvStr.erase(delStrPos - 1, delStr.size() + 1);
196 }
197 return true;
198 }
199 return false;
200}
201
Patrick Williams9638afb2021-02-22 17:16:24 -0600202bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530203{
204 if (userName.empty())
205 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800206 lg2::error("User name is empty");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530207 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
208 Argument::ARGUMENT_VALUE("Null"));
209 }
210 if (usersList.find(userName) == usersList.end())
211 {
212 return false;
213 }
214 return true;
215}
216
Patrick Williams9638afb2021-02-22 17:16:24 -0600217void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530218{
Nan Zhou8a11d992022-10-25 00:07:06 +0000219 if (!isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530220 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800221 lg2::error("User '{USERNAME}' does not exist", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530222 elog<UserNameDoesNotExist>();
223 }
224}
225
Nan Zhouda401fe2022-10-25 00:07:18 +0000226void UserMgr::checkAndThrowForDisallowedGroupCreation(
227 const std::string& groupName)
228{
229 if (groupName.size() > maxSystemGroupNameLength ||
230 !std::regex_match(groupName.c_str(),
nichanghao.nchd9adc732024-01-17 20:24:17 +0800231 std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
Nan Zhouda401fe2022-10-25 00:07:18 +0000232 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800233 lg2::error("Invalid group name '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000234 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
235 Argument::ARGUMENT_VALUE(groupName.c_str()));
236 }
237 checkAndThrowsForGroupChangeAllowed(groupName);
238}
239
Patrick Williams9638afb2021-02-22 17:16:24 -0600240void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530241{
Nan Zhou8a11d992022-10-25 00:07:06 +0000242 if (isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530243 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800244 lg2::error("User '{USERNAME}' already exists", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530245 elog<UserNameExists>();
246 }
247}
248
249void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600250 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530251{
252 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
253 groupNames.end())
254 {
255 if (userName.length() > ipmiMaxUserNameLen)
256 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800257 lg2::error("User '{USERNAME}' exceeds IPMI username length limit "
258 "({LENGTH} > {LIMIT})",
259 "USERNAME", userName, "LENGTH", userName.length(),
260 "LIMIT", ipmiMaxUserNameLen);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530261 elog<UserNameGroupFail>(
262 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
263 "IPMI length"));
264 }
265 }
266 if (userName.length() > systemMaxUserNameLen)
267 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800268 lg2::error("User '{USERNAME}' exceeds system username length limit "
269 "({LENGTH} > {LIMIT})",
270 "USERNAME", userName, "LENGTH", userName.length(), "LIMIT",
271 systemMaxUserNameLen);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530272 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
273 Argument::ARGUMENT_VALUE("Invalid length"));
274 }
275 if (!std::regex_match(userName.c_str(),
nichanghao.nchd9adc732024-01-17 20:24:17 +0800276 std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530277 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800278 lg2::error("Invalid username '{USERNAME}'", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530279 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
280 Argument::ARGUMENT_VALUE("Invalid data"));
281 }
282}
283
Patrick Williams88a82db2025-02-01 08:22:37 -0500284void UserMgr::throwForMaxGrpUserCount(
285 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530286{
287 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
288 groupNames.end())
289 {
290 if (getIpmiUsersCount() >= ipmiMaxUsers)
291 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800292 lg2::error("IPMI user limit reached");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530293 elog<NoResource>(
294 xyz::openbmc_project::User::Common::NoResource::REASON(
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800295 "IPMI user limit reached"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530296 }
297 }
298 else
299 {
300 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
301 (maxSystemUsers - ipmiMaxUsers))
302 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800303 lg2::error("Non-ipmi User limit reached");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530304 elog<NoResource>(
305 xyz::openbmc_project::User::Common::NoResource::REASON(
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800306 "Non-ipmi user limit reached"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530307 }
308 }
309 return;
310}
311
Patrick Williams9638afb2021-02-22 17:16:24 -0600312void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530313{
314 if (!priv.empty() &&
315 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
316 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800317 lg2::error("Invalid privilege '{PRIVILEGE}'", "PRIVILEGE", priv);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530318 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
319 Argument::ARGUMENT_VALUE(priv.c_str()));
320 }
321}
322
Patrick Williams9638afb2021-02-22 17:16:24 -0600323void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530324{
Patrick Williams9638afb2021-02-22 17:16:24 -0600325 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530326 {
327 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
328 groupsMgr.end())
329 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800330 lg2::error("Invalid Group Name '{GROUPNAME}'", "GROUPNAME", group);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530331 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
332 Argument::ARGUMENT_VALUE(group.c_str()));
333 }
334 }
335}
336
Nan Zhouda401fe2022-10-25 00:07:18 +0000337std::vector<std::string> UserMgr::readAllGroupsOnSystem()
338{
339 std::vector<std::string> allGroups = {predefinedGroups.begin(),
340 predefinedGroups.end()};
341 // rewinds to the beginning of the group database
342 setgrent();
343 struct group* gr = getgrent();
344 while (gr != nullptr)
345 {
346 std::string group(gr->gr_name);
347 for (std::string_view prefix : allowedGroupPrefix)
348 {
349 if (group.starts_with(prefix))
350 {
351 allGroups.push_back(gr->gr_name);
352 }
353 }
354 gr = getgrent();
355 }
356 // close the group database
357 endgrent();
358 return allGroups;
359}
360
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530361void UserMgr::createUser(std::string userName,
362 std::vector<std::string> groupNames, std::string priv,
363 bool enabled)
364{
365 throwForInvalidPrivilege(priv);
366 throwForInvalidGroups(groupNames);
367 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500368 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530369 throwForUserExists(userName);
370 throwForUserNameConstraints(userName, groupNames);
371 throwForMaxGrpUserCount(groupNames);
372
373 std::string groups = getCSVFromVector(groupNames);
374 bool sshRequested = removeStringFromCSV(groups, grpSsh);
375
376 // treat privilege as a group - This is to avoid using different file to
377 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530378 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530379 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530380 if (groups.size() != 0)
381 {
382 groups += ",";
383 }
384 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530385 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530386 try
387 {
Nan Zhou49c81362022-10-25 00:07:08 +0000388 executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530389 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600390 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530391 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800392 lg2::error("Unable to create new user '{USERNAME}'", "USERNAME",
393 userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530394 elog<InternalFailure>();
395 }
396
397 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530398 sdbusplus::message::object_path tempObjPath(usersObjPath);
399 tempObjPath /= userName;
400 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530401 std::sort(groupNames.begin(), groupNames.end());
402 usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +0000403 userName, std::make_unique<phosphor::user::Users>(
404 bus, userObj.c_str(), groupNames, priv, enabled, *this));
Abhilash Raju93804eb2024-10-01 00:24:43 -0500405 serializer.store();
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800406 lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530407 return;
408}
409
410void UserMgr::deleteUser(std::string userName)
411{
412 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500413 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530414 throwForUserDoesNotExist(userName);
415 try
416 {
Jayanth Othayothac921a52023-07-21 03:48:55 -0500417 // Clear user fail records
418 executeUserClearFailRecords(userName.c_str());
Patrick Williams4b294622023-08-08 10:19:18 -0500419
420 executeUserDelete(userName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530421 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600422 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530423 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800424 lg2::error("Delete User '{USERNAME}' failed", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530425 elog<InternalFailure>();
426 }
427
428 usersList.erase(userName);
Abhilash Raju93804eb2024-10-01 00:24:43 -0500429 serializer.store();
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800430 lg2::info("User '{USERNAME}' deleted successfully", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530431 return;
432}
433
Nan Zhouda401fe2022-10-25 00:07:18 +0000434void UserMgr::checkDeleteGroupConstraints(const std::string& groupName)
435{
436 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) ==
437 groupsMgr.end())
438 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800439 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000440 elog<GroupNameDoesNotExists>();
441 }
442 checkAndThrowsForGroupChangeAllowed(groupName);
443}
444
445void UserMgr::deleteGroup(std::string groupName)
446{
447 checkDeleteGroupConstraints(groupName);
448 try
449 {
450 executeGroupDeletion(groupName.c_str());
451 }
452 catch (const InternalFailure& e)
453 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800454 lg2::error("Failed to delete group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000455 elog<InternalFailure>();
456 }
457
458 groupsMgr.erase(std::find(groupsMgr.begin(), groupsMgr.end(), groupName));
459 UserMgrIface::allGroups(groupsMgr);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800460 lg2::info("Successfully deleted group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000461}
462
463void UserMgr::checkCreateGroupConstraints(const std::string& groupName)
464{
465 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) !=
466 groupsMgr.end())
467 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800468 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000469 elog<GroupNameExists>();
470 }
471 checkAndThrowForDisallowedGroupCreation(groupName);
472 if (groupsMgr.size() >= maxSystemGroupCount)
473 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800474 lg2::error("Group limit reached");
Nan Zhouda401fe2022-10-25 00:07:18 +0000475 elog<NoResource>(xyz::openbmc_project::User::Common::NoResource::REASON(
476 "Group limit reached"));
477 }
478}
479
480void UserMgr::createGroup(std::string groupName)
481{
482 checkCreateGroupConstraints(groupName);
483 try
484 {
485 executeGroupCreation(groupName.c_str());
486 }
487 catch (const InternalFailure& e)
488 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800489 lg2::error("Failed to create group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000490 elog<InternalFailure>();
491 }
492 groupsMgr.push_back(groupName);
493 UserMgrIface::allGroups(groupsMgr);
494}
495
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530496void UserMgr::renameUser(std::string userName, std::string newUserName)
497{
498 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500499 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530500 throwForUserDoesNotExist(userName);
501 throwForUserExists(newUserName);
502 throwForUserNameConstraints(newUserName,
503 usersList[userName].get()->userGroups());
504 try
505 {
Nan Zhouf25443e2022-10-25 00:07:11 +0000506 executeUserRename(userName.c_str(), newUserName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530507 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600508 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530509 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800510 lg2::error("Rename '{USERNAME}' to '{NEWUSERNAME}' failed", "USERNAME",
511 userName, "NEWUSERNAME", newUserName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530512 elog<InternalFailure>();
513 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600514 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530515 std::string priv = user.get()->userPrivilege();
516 std::vector<std::string> groupNames = user.get()->userGroups();
517 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530518 sdbusplus::message::object_path tempObjPath(usersObjPath);
519 tempObjPath /= newUserName;
520 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530521 // Special group 'ipmi' needs a way to identify user renamed, in order to
522 // update encrypted password. It can't rely only on InterfacesRemoved &
523 // InterfacesAdded. So first send out userRenamed signal.
524 this->userRenamed(userName, newUserName);
525 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000526 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
527 bus, newUserObj.c_str(), groupNames,
528 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530529 return;
530}
531
Patrick Williams9638afb2021-02-22 17:16:24 -0600532void UserMgr::updateGroupsAndPriv(const std::string& userName,
Nan Zhoufef63032022-10-25 00:07:12 +0000533 std::vector<std::string> groupNames,
Patrick Williams9638afb2021-02-22 17:16:24 -0600534 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530535{
536 throwForInvalidPrivilege(priv);
537 throwForInvalidGroups(groupNames);
538 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500539 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530540 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600541 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530542 usersList[userName].get()->userGroups();
543 std::vector<std::string> groupDiff;
544 // Note: already dealing with sorted group lists.
545 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
546 groupNames.begin(), groupNames.end(),
547 std::back_inserter(groupDiff));
548 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
549 groupDiff.end())
550 {
551 throwForUserNameConstraints(userName, groupNames);
552 throwForMaxGrpUserCount(groupNames);
553 }
554
555 std::string groups = getCSVFromVector(groupNames);
556 bool sshRequested = removeStringFromCSV(groups, grpSsh);
557
558 // treat privilege as a group - This is to avoid using different file to
559 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530560 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530561 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530562 if (groups.size() != 0)
563 {
564 groups += ",";
565 }
566 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530567 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530568 try
569 {
Nan Zhoufef63032022-10-25 00:07:12 +0000570 executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530571 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600572 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530573 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800574 lg2::error(
575 "Unable to modify user privilege / groups for user '{USERNAME}'",
576 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530577 elog<InternalFailure>();
578 }
579
Nan Zhoufef63032022-10-25 00:07:12 +0000580 std::sort(groupNames.begin(), groupNames.end());
581 usersList[userName]->setUserGroups(groupNames);
582 usersList[userName]->setUserPrivilege(priv);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800583 lg2::info("User '{USERNAME}' groups / privilege updated successfully",
584 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530585}
586
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530587uint8_t UserMgr::minPasswordLength(uint8_t value)
588{
589 if (value == AccountPolicyIface::minPasswordLength())
590 {
591 return value;
592 }
Chandramohan Harkude9ca86922025-05-18 13:09:25 +0530593 if (value < minPasswdLength || value > maxPasswdLength)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530594 {
Chandramohan Harkude9ca86922025-05-18 13:09:25 +0530595 std::string valueStr = std::to_string(value);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800596 lg2::error("Attempting to set minPasswordLength to {VALUE}, less than "
Chandramohan Harkude9ca86922025-05-18 13:09:25 +0530597 "{MINPASSWORDLENGTH} or greater than {MAXPASSWORDLENGTH}",
598 "VALUE", value, "MINPASSWORDLENGTH", minPasswdLength,
599 "MAXPASSWORDLENGTH", maxPasswdLength);
600 elog<InvalidArgument>(Argument::ARGUMENT_NAME("minPasswordLength"),
601 Argument::ARGUMENT_VALUE(valueStr.data()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530602 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700603 if (setPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
604 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530605 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800606 lg2::error("Unable to set minPasswordLength to {VALUE}", "VALUE",
607 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530608 elog<InternalFailure>();
609 }
610 return AccountPolicyIface::minPasswordLength(value);
611}
612
613uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
614{
615 if (value == AccountPolicyIface::rememberOldPasswordTimes())
616 {
617 return value;
618 }
Jason M. Bills3b280ec2023-08-15 16:15:48 -0700619 if (setPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
620 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530621 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800622 lg2::error("Unable to set rememberOldPasswordTimes to {VALUE}", "VALUE",
623 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530624 elog<InternalFailure>();
625 }
626 return AccountPolicyIface::rememberOldPasswordTimes(value);
627}
628
629uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
630{
631 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
632 {
633 return value;
634 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700635 if (setPamModuleConfValue(faillockConfigFile, maxFailedAttempt,
636 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530637 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800638 lg2::error("Unable to set maxLoginAttemptBeforeLockout to {VALUE}",
639 "VALUE", value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530640 elog<InternalFailure>();
641 }
642 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
643}
644
645uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
646{
647 if (value == AccountPolicyIface::accountUnlockTimeout())
648 {
649 return value;
650 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700651 if (setPamModuleConfValue(faillockConfigFile, unlockTimeout,
652 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530653 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800654 lg2::error("Unable to set accountUnlockTimeout to {VALUE}", "VALUE",
655 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530656 elog<InternalFailure>();
657 }
658 return AccountPolicyIface::accountUnlockTimeout(value);
659}
660
Jason M. Bills2d042d12023-03-28 15:32:45 -0700661int UserMgr::getPamModuleConfValue(const std::string& confFile,
662 const std::string& argName,
663 std::string& argValue)
664{
665 std::ifstream fileToRead(confFile, std::ios::in);
666 if (!fileToRead.is_open())
667 {
668 lg2::error("Failed to open pam configuration file {FILENAME}",
669 "FILENAME", confFile);
670 return failure;
671 }
672 std::string line;
673 auto argSearch = argName + "=";
674 size_t startPos = 0;
675 size_t endPos = 0;
676 while (getline(fileToRead, line))
677 {
678 // skip comments section starting with #
679 if ((startPos = line.find('#')) != std::string::npos)
680 {
681 if (startPos == 0)
682 {
683 continue;
684 }
685 // skip comments after meaningful section and process those
686 line = line.substr(0, startPos);
687 }
688 if ((startPos = line.find(argSearch)) != std::string::npos)
689 {
690 if ((endPos = line.find(' ', startPos)) == std::string::npos)
691 {
692 endPos = line.size();
693 }
694 startPos += argSearch.size();
695 argValue = line.substr(startPos, endPos - startPos);
696 return success;
697 }
698 }
699 return failure;
700}
701
Jason M. Bills2d042d12023-03-28 15:32:45 -0700702int UserMgr::setPamModuleConfValue(const std::string& confFile,
703 const std::string& argName,
704 const std::string& argValue)
705{
706 std::string tmpConfFile = confFile + "_tmp";
707 std::ifstream fileToRead(confFile, std::ios::in);
708 std::ofstream fileToWrite(tmpConfFile, std::ios::out);
709 if (!fileToRead.is_open() || !fileToWrite.is_open())
710 {
711 lg2::error("Failed to open pam configuration file {FILENAME}",
712 "FILENAME", confFile);
Jason M. Bills17b88272023-05-22 14:24:02 -0700713 // Delete the unused tmp file
714 std::remove(tmpConfFile.c_str());
Jason M. Bills2d042d12023-03-28 15:32:45 -0700715 return failure;
716 }
717 std::string line;
718 auto argSearch = argName + "=";
719 size_t startPos = 0;
720 size_t endPos = 0;
721 bool found = false;
722 while (getline(fileToRead, line))
723 {
724 // skip comments section starting with #
725 if ((startPos = line.find('#')) != std::string::npos)
726 {
727 if (startPos == 0)
728 {
729 fileToWrite << line << std::endl;
730 continue;
731 }
732 // skip comments after meaningful section and process those
733 line = line.substr(0, startPos);
734 }
735 if ((startPos = line.find(argSearch)) != std::string::npos)
736 {
737 if ((endPos = line.find(' ', startPos)) == std::string::npos)
738 {
739 endPos = line.size();
740 }
741 startPos += argSearch.size();
742 fileToWrite << line.substr(0, startPos) << argValue
743 << line.substr(endPos, line.size() - endPos)
744 << std::endl;
745 found = true;
746 continue;
747 }
748 fileToWrite << line << std::endl;
749 }
750 fileToWrite.close();
751 fileToRead.close();
752 if (found)
753 {
754 if (std::rename(tmpConfFile.c_str(), confFile.c_str()) == 0)
755 {
756 return success;
757 }
758 }
Jason M. Bills17b88272023-05-22 14:24:02 -0700759 // No changes, so delete the unused tmp file
760 std::remove(tmpConfFile.c_str());
Jason M. Bills2d042d12023-03-28 15:32:45 -0700761 return failure;
762}
763
Patrick Williams9638afb2021-02-22 17:16:24 -0600764void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530765{
766 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500767 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530768 throwForUserDoesNotExist(userName);
769 try
770 {
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000771 executeUserModifyUserEnable(userName.c_str(), enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530772 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600773 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530774 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800775 lg2::error("Unable to modify user enabled state for '{USERNAME}'",
776 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530777 elog<InternalFailure>();
778 }
779
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000780 usersList[userName]->setUserEnabled(enabled);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800781 lg2::info("User '{USERNAME}' has been {STATUS}", "USERNAME", userName,
782 "STATUS", enabled ? "Enabled" : "Disabled");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530783}
784
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530785/**
Jason M. Bills2d042d12023-03-28 15:32:45 -0700786 * faillock app will provide the user failed login list with when the attempt
787 * was made, the type, the source, and if it's valid.
788 *
789 * Valid in this case means that the attempt was made within the fail_interval
790 * time. So, we can check this list for the number of valid entries (lines
791 * ending with 'V') compared to the maximum allowed to determine if the user is
792 * locked out.
793 *
794 * This data is only refreshed when an attempt is made, so if the user appears
795 * to be locked out, we must also check if the most recent attempt was older
796 * than the unlock_time to know if the user has since been unlocked.
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530797 **/
Jason M. Bills2d042d12023-03-28 15:32:45 -0700798bool UserMgr::parseFaillockForLockout(
799 const std::vector<std::string>& faillockOutput)
800{
801 uint16_t failAttempts = 0;
802 time_t lastFailedAttempt{};
803 for (const std::string& line : faillockOutput)
804 {
805 if (!line.ends_with("V"))
806 {
807 continue;
808 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530809
Jason M. Bills2d042d12023-03-28 15:32:45 -0700810 // Count this failed attempt
811 failAttempts++;
812
813 // Update the last attempt time
814 // First get the "when" which is the first two words (date and time)
815 size_t pos = line.find(" ");
816 if (pos == std::string::npos)
817 {
818 continue;
819 }
820 pos = line.find(" ", pos + 1);
821 if (pos == std::string::npos)
822 {
823 continue;
824 }
825 std::string failDateTime = line.substr(0, pos);
826
827 // NOTE: Cannot use std::get_time() here as the implementation of %y in
828 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
829 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
830 std::tm tmStruct = {};
831 if (!strptime(failDateTime.c_str(), "%F %T", &tmStruct))
832 {
833 lg2::error("Failed to parse latest failure date/time");
834 elog<InternalFailure>();
835 }
836
837 time_t failTimestamp = std::mktime(&tmStruct);
838 lastFailedAttempt = std::max(failTimestamp, lastFailedAttempt);
839 }
840
841 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
842 {
843 return false;
844 }
845
846 if (lastFailedAttempt +
847 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
848 std::time(NULL))
849 {
850 return false;
851 }
852
853 return true;
854}
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530855
Patrick Williams9638afb2021-02-22 17:16:24 -0600856bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530857{
858 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500859 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800860 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
861 {
862 return false;
863 }
864
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530865 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800866 try
867 {
Nan Zhoua2953032022-11-11 21:50:32 +0000868 output = getFailedAttempt(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800869 }
870 catch (const InternalFailure& e)
871 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800872 lg2::error("Unable to read login failure counter");
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800873 elog<InternalFailure>();
874 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530875
Jason M. Bills2d042d12023-03-28 15:32:45 -0700876 return parseFaillockForLockout(output);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530877}
878
Patrick Williams9638afb2021-02-22 17:16:24 -0600879bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
880 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530881{
882 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500883 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530884 if (value == true)
885 {
886 return userLockedForFailedAttempt(userName);
887 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530888
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800889 try
890 {
Jayanth Othayothac921a52023-07-21 03:48:55 -0500891 // Clear user fail records
892 executeUserClearFailRecords(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800893 }
894 catch (const InternalFailure& e)
895 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800896 lg2::error("Unable to reset login failure counter");
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800897 elog<InternalFailure>();
898 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530899
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530900 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530901}
902
Patrick Williams9638afb2021-02-22 17:16:24 -0600903bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600904{
905 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500906 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600907
Patrick Williams66addf22024-12-18 11:21:29 -0500908 struct spwd spwd{};
Patrick Williams9638afb2021-02-22 17:16:24 -0600909 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600910 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
George Liu34e6ccd2024-09-18 09:22:53 +0800911 if (buflen <= 0)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600912 {
913 // Use a default size if there is no hard limit suggested by sysconf()
914 buflen = 1024;
915 }
916 std::vector<char> buffer(buflen);
Patrick Williams16c2b682024-08-16 15:20:56 -0400917 auto status =
918 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600919 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
920 // If no matching password record was found, these functions return 0
921 // and store NULL in *spwdPtr
922 if ((status == 0) && (&spwd == spwdPtr))
923 {
924 // Determine password validity per "chage" docs, where:
925 // spwd.sp_lstchg == 0 means password is expired, and
926 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000927 constexpr long secondsPerDay = 60 * 60 * 24;
928 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600929 if ((spwd.sp_lstchg == 0) ||
930 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
931 {
932 return true;
933 }
934 }
935 else
936 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000937 // User entry is missing in /etc/shadow, indicating no SHA password.
938 // Treat this as new user without password entry in /etc/shadow
939 // TODO: Add property to indicate user password was not set yet
940 // https://github.com/openbmc/phosphor-user-manager/issues/8
941 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600942 }
943
944 return false;
945}
946
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530947UserSSHLists UserMgr::getUserAndSshGrpList()
948{
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{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530951
952 std::vector<std::string> userList;
953 std::vector<std::string> sshUsersList;
954 struct passwd pw, *pwp = nullptr;
955 std::array<char, 1024> buffer{};
956
957 phosphor::user::File passwd(passwdFileName, "r");
958 if ((passwd)() == NULL)
959 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800960 lg2::error("Error opening {FILENAME}", "FILENAME", passwdFileName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530961 elog<InternalFailure>();
962 }
963
964 while (true)
965 {
966 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
967 &pwp);
968 if ((r != 0) || (pwp == NULL))
969 {
970 // Any error, break the loop.
971 break;
972 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530973#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530974 // Add all users whose UID >= 1000 and < 65534
975 // and special UID 0.
976 if ((pwp->pw_uid == 0) ||
977 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530978#else
979 // Add all users whose UID >=1000 and < 65534
980 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
981#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530982 {
983 std::string userName(pwp->pw_name);
984 userList.emplace_back(userName);
985
986 // ssh doesn't have separate group. Check login shell entry to
987 // get all users list which are member of ssh group.
988 std::string loginShell(pwp->pw_shell);
989 if (loginShell == "/bin/sh")
990 {
991 sshUsersList.emplace_back(userName);
992 }
993 }
994 }
995 endpwent();
996 return std::make_pair(std::move(userList), std::move(sshUsersList));
997}
998
999size_t UserMgr::getIpmiUsersCount()
1000{
1001 std::vector<std::string> userList = getUsersInGroup("ipmi");
1002 return userList.size();
1003}
1004
Nan Zhou49c81362022-10-25 00:07:08 +00001005size_t UserMgr::getNonIpmiUsersCount()
1006{
1007 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
1008 return usersList.size() - ipmiUsers.size();
1009}
1010
Patrick Williams9638afb2021-02-22 17:16:24 -06001011bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301012{
1013 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001014 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301015 std::array<char, 4096> buffer{};
1016 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -06001017 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301018 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
1019 buffer.max_size(), &resultPtr);
1020 if (!status && (&spwd == resultPtr))
1021 {
Ivan Moiseevc4183b82024-12-25 11:59:49 +03001022 // according to chage/usermod code -1 means that account does not expire
1023 // https://github.com/shadow-maint/shadow/blob/7a796897e52293efe9e210ab8da32b7aefe65591/src/chage.c
1024 if (resultPtr->sp_expire < 0)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301025 {
Ivan Moiseevc4183b82024-12-25 11:59:49 +03001026 return true;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301027 }
Ivan Moiseevc4183b82024-12-25 11:59:49 +03001028
1029 // check account expiration date against current date
1030 if (resultPtr->sp_expire > currentDate())
1031 {
1032 return true;
1033 }
1034
1035 return false;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301036 }
1037 return false; // assume user is disabled for any error.
1038}
1039
Patrick Williams9638afb2021-02-22 17:16:24 -06001040std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301041{
1042 std::vector<std::string> usersInGroup;
1043 // Should be more than enough to get the pwd structure.
1044 std::array<char, 4096> buffer{};
1045 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -06001046 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301047
1048 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1049 buffer.max_size(), &resultPtr);
1050
1051 if (!status && (&grp == resultPtr))
1052 {
1053 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
1054 {
1055 usersInGroup.emplace_back(*(grp.gr_mem));
1056 }
1057 }
1058 else
1059 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001060 lg2::error("Group '{GROUPNAME}' not found", "GROUPNAME", groupName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301061 // Don't throw error, just return empty userList - fallback
1062 }
1063 return usersInGroup;
1064}
1065
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001066DbusUserObj UserMgr::getPrivilegeMapperObject(void)
1067{
1068 DbusUserObj objects;
1069 try
1070 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001071 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
1072 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001073
Patrick Williams16c2b682024-08-16 15:20:56 -04001074 auto ldapMgmtService =
1075 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001076 auto method = bus.new_method_call(
1077 ldapMgmtService.c_str(), ldapMgrObjBasePath,
1078 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1079
1080 auto reply = bus.call(method);
1081 reply.read(objects);
1082 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001083 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001084 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001085 lg2::error("Unable to get the User Service: {ERR}", "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001086 throw;
1087 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001088 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001089 {
Manojkiran Eda46e773a2024-06-17 14:45:33 +05301090 lg2::error("Failed to execute GetManagedObjects at {PATH}: {ERR}",
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001091 "PATH", ldapMgrObjBasePath, "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001092 throw;
1093 }
1094 return objects;
1095}
1096
Patrick Williams9638afb2021-02-22 17:16:24 -06001097std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001098{
1099 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1100 objMapperInterface, "GetObject");
1101
1102 mapperCall.append(std::move(path));
1103 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1104
1105 auto mapperResponseMsg = bus.call(mapperCall);
1106
1107 if (mapperResponseMsg.is_method_error())
1108 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001109 lg2::error("Error in mapper call");
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001110 elog<InternalFailure>();
1111 }
1112
1113 std::map<std::string, std::vector<std::string>> mapperResponse;
1114 mapperResponseMsg.read(mapperResponse);
1115
1116 if (mapperResponse.begin() == mapperResponse.end())
1117 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001118 lg2::error("Invalid response from mapper");
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001119 elog<InternalFailure>();
1120 }
1121
1122 return mapperResponse.begin()->first;
1123}
1124
Alexander Filippov75626582022-02-09 18:42:37 +03001125gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
1126{
1127 static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1128 if (buflen <= 0)
1129 {
1130 // Use a default size if there is no hard limit suggested by sysconf()
1131 buflen = 1024;
1132 }
1133
1134 struct passwd pwd;
1135 struct passwd* pwdPtr = nullptr;
1136 std::vector<char> buffer(buflen);
1137
1138 auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(),
1139 buffer.size(), &pwdPtr);
1140 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
1141 // If no matching password record was found, these functions return 0
1142 // and store NULL in *pwdPtr
1143 if (!status && (&pwd == pwdPtr))
1144 {
1145 return pwd.pw_gid;
1146 }
1147
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001148 lg2::error("User {USERNAME} does not exist", "USERNAME", userName);
Alexander Filippov75626582022-02-09 18:42:37 +03001149 elog<UserNameDoesNotExist>();
1150}
1151
1152bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
1153 const std::string& groupName) const
1154{
1155 static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1156 if (buflen <= 0)
1157 {
1158 // Use a default size if there is no hard limit suggested by sysconf()
1159 buflen = 1024;
1160 }
1161
1162 struct group grp;
1163 struct group* grpPtr = nullptr;
1164 std::vector<char> buffer(buflen);
1165
1166 auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1167 buffer.size(), &grpPtr);
1168
1169 // Groups with a lot of members may require a buffer of bigger size than
1170 // suggested by _SC_GETGR_R_SIZE_MAX.
1171 // 32K should be enough for about 2K members.
1172 constexpr auto maxBufferLength = 32 * 1024;
1173 while (status == ERANGE && buflen < maxBufferLength)
1174 {
1175 buflen *= 2;
1176 buffer.resize(buflen);
1177
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001178 lg2::debug("Increase buffer for getgrnam_r() to {SIZE}", "SIZE",
1179 buflen);
Alexander Filippov75626582022-02-09 18:42:37 +03001180
1181 status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1182 buffer.size(), &grpPtr);
1183 }
1184
1185 // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
1186 // If no matching group record was found, these functions return 0
1187 // and store NULL in *grpPtr
1188 if (!status && (&grp == grpPtr))
1189 {
1190 if (primaryGid == grp.gr_gid)
1191 {
1192 return true;
1193 }
1194
1195 for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
1196 {
1197 if (userName == grp.gr_mem[i])
1198 {
1199 return true;
1200 }
1201 }
1202 }
1203 else if (status == ERANGE)
1204 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001205 lg2::error("Group info of {GROUP} requires too much memory", "GROUP",
1206 groupName);
Alexander Filippov75626582022-02-09 18:42:37 +03001207 }
1208 else
1209 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001210 lg2::error("Group {GROUP} does not exist", "GROUP", groupName);
Alexander Filippov75626582022-02-09 18:42:37 +03001211 }
1212
1213 return false;
1214}
1215
Nan Zhouda401fe2022-10-25 00:07:18 +00001216void UserMgr::executeGroupCreation(const char* groupName)
1217{
1218 executeCmd("/usr/sbin/groupadd", groupName);
1219}
1220
1221void UserMgr::executeGroupDeletion(const char* groupName)
1222{
1223 executeCmd("/usr/sbin/groupdel", groupName);
1224}
1225
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001226UserInfoMap UserMgr::getUserInfo(std::string userName)
1227{
1228 UserInfoMap userInfo;
1229 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001230 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001231 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001232 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001233 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1234 userInfo.emplace("UserGroups", user.get()->userGroups());
1235 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1236 userInfo.emplace("UserLockedForFailedAttempt",
1237 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001238 userInfo.emplace("UserPasswordExpired",
1239 user.get()->userPasswordExpired());
Abhilash Rajua1a754c2024-07-25 05:43:40 -05001240 userInfo.emplace("TOTPSecretkeyRequired",
1241 user.get()->secretKeyGenerationRequired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001242 userInfo.emplace("RemoteUser", false);
1243 }
1244 else
1245 {
Alexander Filippov75626582022-02-09 18:42:37 +03001246 auto primaryGid = getPrimaryGroup(userName);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001247
1248 DbusUserObj objects = getPrivilegeMapperObject();
1249
Ravi Teja5fe724a2019-05-07 05:14:42 -05001250 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001251 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001252
1253 try
1254 {
Alexander Filippov75626582022-02-09 18:42:37 +03001255 for (const auto& [path, interfaces] : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001256 {
Alexander Filippov75626582022-02-09 18:42:37 +03001257 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
1258 if (it != interfaces.end())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001259 {
Alexander Filippov75626582022-02-09 18:42:37 +03001260 auto propIt = it->second.find("Enabled");
1261 if (propIt != it->second.end() &&
1262 std::get<bool>(propIt->second))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001263 {
Alexander Filippov75626582022-02-09 18:42:37 +03001264 ldapConfigPath = path.str + '/';
1265 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001266 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001267 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001268 }
1269
1270 if (ldapConfigPath.empty())
1271 {
1272 return userInfo;
1273 }
1274
Alexander Filippov75626582022-02-09 18:42:37 +03001275 for (const auto& [path, interfaces] : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001276 {
Alexander Filippov75626582022-02-09 18:42:37 +03001277 if (!path.str.starts_with(ldapConfigPath))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001278 {
Alexander Filippov75626582022-02-09 18:42:37 +03001279 continue;
1280 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001281
Alexander Filippov75626582022-02-09 18:42:37 +03001282 auto it = interfaces.find(
1283 "xyz.openbmc_project.User.PrivilegeMapperEntry");
1284 if (it != interfaces.end())
1285 {
1286 std::string privilege;
1287 std::string groupName;
1288
1289 for (const auto& [propName, propValue] : it->second)
1290 {
1291 if (propName == "GroupName")
Ravi Teja5fe724a2019-05-07 05:14:42 -05001292 {
Alexander Filippov75626582022-02-09 18:42:37 +03001293 groupName = std::get<std::string>(propValue);
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001294 }
Alexander Filippov75626582022-02-09 18:42:37 +03001295 else if (propName == "Privilege")
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001296 {
Alexander Filippov75626582022-02-09 18:42:37 +03001297 privilege = std::get<std::string>(propValue);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001298 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001299 }
Alexander Filippov75626582022-02-09 18:42:37 +03001300
1301 if (!groupName.empty() && !privilege.empty() &&
1302 isGroupMember(userName, primaryGid, groupName))
1303 {
1304 userPrivilege = privilege;
1305 break;
1306 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001307 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001308 if (!userPrivilege.empty())
1309 {
1310 break;
1311 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001312 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001313
Jiaqing Zhao56862062022-05-31 19:16:09 +08001314 if (!userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001315 {
Jiaqing Zhao56862062022-05-31 19:16:09 +08001316 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001317 }
Jiaqing Zhao56862062022-05-31 19:16:09 +08001318 else
1319 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001320 lg2::warning("LDAP group privilege mapping does not exist, "
1321 "default \"priv-user\" is used");
Jiaqing Zhao56862062022-05-31 19:16:09 +08001322 userInfo.emplace("UserPrivilege", "priv-user");
1323 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001324 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001325 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001326 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001327 lg2::error("Error while accessing variant: {ERR}", "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001328 elog<InternalFailure>();
1329 }
1330 userInfo.emplace("RemoteUser", true);
1331 }
1332
1333 return userInfo;
1334}
1335
Nan Zhou4bc69812022-10-25 00:07:13 +00001336void UserMgr::initializeAccountPolicy()
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301337{
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301338 std::string valueStr;
1339 auto value = minPasswdLength;
1340 unsigned long tmp = 0;
Jason M. Bills2d042d12023-03-28 15:32:45 -07001341 if (getPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
1342 valueStr) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301343 {
1344 AccountPolicyIface::minPasswordLength(minPasswdLength);
1345 }
1346 else
1347 {
1348 try
1349 {
1350 tmp = std::stoul(valueStr, nullptr);
1351 if (tmp > std::numeric_limits<decltype(value)>::max())
1352 {
1353 throw std::out_of_range("Out of range");
1354 }
1355 value = static_cast<decltype(value)>(tmp);
1356 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001357 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301358 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001359 lg2::error("Exception for MinPasswordLength: {ERR}", "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001360 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301361 }
1362 AccountPolicyIface::minPasswordLength(value);
1363 }
1364 valueStr.clear();
Jason M. Bills3b280ec2023-08-15 16:15:48 -07001365 if (getPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
1366 valueStr) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301367 {
1368 AccountPolicyIface::rememberOldPasswordTimes(0);
1369 }
1370 else
1371 {
1372 value = 0;
1373 try
1374 {
1375 tmp = std::stoul(valueStr, nullptr);
1376 if (tmp > std::numeric_limits<decltype(value)>::max())
1377 {
1378 throw std::out_of_range("Out of range");
1379 }
1380 value = static_cast<decltype(value)>(tmp);
1381 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001382 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301383 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001384 lg2::error("Exception for RememberOldPasswordTimes: {ERR}", "ERR",
1385 e);
Patrick Venture045b1122018-10-16 15:59:29 -07001386 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301387 }
1388 AccountPolicyIface::rememberOldPasswordTimes(value);
1389 }
1390 valueStr.clear();
Jason M. Bills2d042d12023-03-28 15:32:45 -07001391 if (getPamModuleConfValue(faillockConfigFile, maxFailedAttempt, valueStr) !=
1392 success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301393 {
1394 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1395 }
1396 else
1397 {
1398 uint16_t value16 = 0;
1399 try
1400 {
1401 tmp = std::stoul(valueStr, nullptr);
1402 if (tmp > std::numeric_limits<decltype(value16)>::max())
1403 {
1404 throw std::out_of_range("Out of range");
1405 }
1406 value16 = static_cast<decltype(value16)>(tmp);
1407 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001408 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301409 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001410 lg2::error("Exception for MaxLoginAttemptBeforLockout: {ERR}",
1411 "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001412 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301413 }
1414 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1415 }
1416 valueStr.clear();
Jason M. Bills2d042d12023-03-28 15:32:45 -07001417 if (getPamModuleConfValue(faillockConfigFile, unlockTimeout, valueStr) !=
1418 success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301419 {
1420 AccountPolicyIface::accountUnlockTimeout(0);
1421 }
1422 else
1423 {
1424 uint32_t value32 = 0;
1425 try
1426 {
1427 tmp = std::stoul(valueStr, nullptr);
1428 if (tmp > std::numeric_limits<decltype(value32)>::max())
1429 {
1430 throw std::out_of_range("Out of range");
1431 }
1432 value32 = static_cast<decltype(value32)>(tmp);
1433 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001434 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301435 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001436 lg2::error("Exception for AccountUnlockTimeout: {ERR}", "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001437 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301438 }
1439 AccountPolicyIface::accountUnlockTimeout(value32);
1440 }
Nan Zhou4bc69812022-10-25 00:07:13 +00001441}
1442
1443void UserMgr::initUserObjects(void)
1444{
1445 // All user management lock has to be based on /etc/shadow
1446 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1447 std::vector<std::string> userNameList;
1448 std::vector<std::string> sshGrpUsersList;
1449 UserSSHLists userSSHLists = getUserAndSshGrpList();
1450 userNameList = std::move(userSSHLists.first);
1451 sshGrpUsersList = std::move(userSSHLists.second);
1452
1453 if (!userNameList.empty())
1454 {
1455 std::map<std::string, std::vector<std::string>> groupLists;
Nan Zhouda401fe2022-10-25 00:07:18 +00001456 // We only track users that are in the |predefinedGroups|
1457 // The other groups don't contain real BMC users.
1458 for (const char* grp : predefinedGroups)
Nan Zhou4bc69812022-10-25 00:07:13 +00001459 {
1460 if (grp == grpSsh)
1461 {
1462 groupLists.emplace(grp, sshGrpUsersList);
1463 }
1464 else
1465 {
1466 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1467 groupLists.emplace(grp, grpUsersList);
1468 }
1469 }
1470 for (auto& grp : privMgr)
1471 {
1472 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1473 groupLists.emplace(grp, grpUsersList);
1474 }
1475
1476 for (auto& user : userNameList)
1477 {
1478 std::vector<std::string> userGroups;
1479 std::string userPriv;
1480 for (const auto& grp : groupLists)
1481 {
1482 std::vector<std::string> tempGrp = grp.second;
1483 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1484 tempGrp.end())
1485 {
1486 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1487 privMgr.end())
1488 {
1489 userPriv = grp.first;
1490 }
1491 else
1492 {
1493 userGroups.emplace_back(grp.first);
1494 }
1495 }
1496 }
1497 // Add user objects to the Users path.
1498 sdbusplus::message::object_path tempObjPath(usersObjPath);
1499 tempObjPath /= user;
1500 std::string objPath(tempObjPath);
1501 std::sort(userGroups.begin(), userGroups.end());
1502 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1503 bus, objPath.c_str(), userGroups,
1504 userPriv, isUserEnabled(user), *this));
1505 }
1506 }
1507}
1508
Abhilash Raju93804eb2024-10-01 00:24:43 -05001509void UserMgr::load()
1510{
1511 std::optional<std::string> authTypeStr;
1512 if (std::filesystem::exists(mfaConfPath))
1513 {
1514 serializer.load();
1515 serializer.deserialize("authtype", authTypeStr);
1516 }
1517 auto authType =
1518 authTypeStr.transform(MultiFactorAuthConfiguration::convertStringToType)
1519 .value_or(std::optional(MultiFactorAuthType::None));
1520 if (authType)
1521 {
1522 enabled(*authType, true);
1523 }
1524}
1525
Nan Zhou4bc69812022-10-25 00:07:13 +00001526UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1527 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
Abhilash Raju93804eb2024-10-01 00:24:43 -05001528 serializer(mfaConfPath), faillockConfigFile(defaultFaillockConfigFile),
Jason M. Bills3b280ec2023-08-15 16:15:48 -07001529 pwHistoryConfigFile(defaultPWHistoryConfigFile),
Jason M. Bills2d042d12023-03-28 15:32:45 -07001530 pwQualityConfigFile(defaultPWQualityConfigFile)
Abhilash Raju93804eb2024-10-01 00:24:43 -05001531
Nan Zhou4bc69812022-10-25 00:07:13 +00001532{
1533 UserMgrIface::allPrivileges(privMgr);
Nan Zhouda401fe2022-10-25 00:07:18 +00001534 groupsMgr = readAllGroupsOnSystem();
Nan Zhou4bc69812022-10-25 00:07:13 +00001535 std::sort(groupsMgr.begin(), groupsMgr.end());
1536 UserMgrIface::allGroups(groupsMgr);
1537 initializeAccountPolicy();
Abhilash Raju93804eb2024-10-01 00:24:43 -05001538 load();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301539 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301540 // emit the signal
1541 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301542}
1543
Nan Zhou49c81362022-10-25 00:07:08 +00001544void UserMgr::executeUserAdd(const char* userName, const char* groups,
1545 bool sshRequested, bool enabled)
1546{
1547 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1548 // 1970-01-01, that's an implementation-defined behavior
1549 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001550 (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
Nan Zhou49c81362022-10-25 00:07:08 +00001551 (enabled ? "" : "1970-01-01"));
1552}
1553
1554void UserMgr::executeUserDelete(const char* userName)
1555{
1556 executeCmd("/usr/sbin/userdel", userName, "-r");
1557}
1558
Jayanth Othayothac921a52023-07-21 03:48:55 -05001559void UserMgr::executeUserClearFailRecords(const char* userName)
1560{
1561 executeCmd("/usr/sbin/faillock", "--user", userName, "--reset");
1562}
1563
Nan Zhouf25443e2022-10-25 00:07:11 +00001564void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1565{
1566 std::string newHomeDir = "/home/";
1567 newHomeDir += newUserName;
1568 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1569 newHomeDir.c_str(), "-m");
1570}
1571
Nan Zhoufef63032022-10-25 00:07:12 +00001572void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1573 bool sshRequested)
1574{
1575 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001576 (sshRequested ? "/bin/sh" : "/sbin/nologin"));
Nan Zhoufef63032022-10-25 00:07:12 +00001577}
1578
Nan Zhou6b6f2d82022-10-25 00:07:17 +00001579void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1580{
1581 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1582 // 1970-01-01, that's an implementation-defined behavior
1583 executeCmd("/usr/sbin/usermod", userName, "-e",
1584 (enabled ? "" : "1970-01-01"));
1585}
1586
Nan Zhoua2953032022-11-11 21:50:32 +00001587std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1588{
Jason M. Bills2d042d12023-03-28 15:32:45 -07001589 return executeCmd("/usr/sbin/faillock", "--user", userName);
Nan Zhoua2953032022-11-11 21:50:32 +00001590}
1591
Abhilash Rajua1a754c2024-07-25 05:43:40 -05001592MultiFactorAuthType UserMgr::enabled(MultiFactorAuthType value, bool skipSignal)
1593{
1594 if (value == enabled())
1595 {
1596 return value;
1597 }
1598 switch (value)
1599 {
1600 case MultiFactorAuthType::None:
1601 for (auto type : {MultiFactorAuthType::GoogleAuthenticator})
1602 {
1603 for (auto& u : usersList)
1604 {
1605 u.second->enableMultiFactorAuth(type, false);
1606 }
1607 }
1608 break;
1609 default:
1610 for (auto& u : usersList)
1611 {
1612 u.second->enableMultiFactorAuth(value, true);
1613 }
1614 break;
1615 }
Abhilash Raju93804eb2024-10-01 00:24:43 -05001616 serializer.serialize(
1617 "authtype", MultiFactorAuthConfiguration::convertTypeToString(value));
1618 serializer.store();
Abhilash Rajua1a754c2024-07-25 05:43:40 -05001619 return MultiFactorAuthConfigurationIface::enabled(value, skipSignal);
1620}
1621bool UserMgr::secretKeyRequired(std::string userName)
1622{
1623 if (usersList.contains(userName))
1624 {
1625 return usersList[userName]->secretKeyGenerationRequired();
1626 }
1627 return false;
1628}
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301629} // namespace user
1630} // namespace phosphor