blob: f4e745f870d2489e2dc86105fbaa6ab85fc99cd3 [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
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{
Abhilash Raju93804eb2024-10-01 00:24:43 -0500110constexpr auto mfaConfPath = "/var/lib/usr_mgr.conf";
Nan Zhouda401fe2022-10-25 00:07:18 +0000111// 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
Ivan Moiseevc4183b82024-12-25 11:59:49 +0300147long currentDate()
148{
149 const auto date = std::chrono::duration_cast<std::chrono::days>(
150 std::chrono::system_clock::now().time_since_epoch())
151 .count();
152
153 if (date > std::numeric_limits<long>::max())
154 {
155 return std::numeric_limits<long>::max();
156 }
157
158 if (date < std::numeric_limits<long>::min())
159 {
160 return std::numeric_limits<long>::min();
161 }
162
163 return date;
164}
165
Nan Zhouda401fe2022-10-25 00:07:18 +0000166} // namespace
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530167
Nan Zhoue47c09d2022-10-25 00:06:41 +0000168std::string getCSVFromVector(std::span<const std::string> vec)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530169{
Nan Zhoue47c09d2022-10-25 00:06:41 +0000170 if (vec.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530171 {
Nan Zhoue47c09d2022-10-25 00:06:41 +0000172 return "";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530173 }
Nan Zhoue47c09d2022-10-25 00:06:41 +0000174 return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
175 [](std::string&& val, std::string_view element) {
Patrick Williams16c2b682024-08-16 15:20:56 -0400176 val += ',';
177 val += element;
178 return val;
179 });
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530180}
181
Nan Zhou332fb9d2022-10-25 00:07:03 +0000182bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530183{
184 std::string::size_type delStrPos = csvStr.find(delStr);
185 if (delStrPos != std::string::npos)
186 {
187 // need to also delete the comma char
188 if (delStrPos == 0)
189 {
190 csvStr.erase(delStrPos, delStr.size() + 1);
191 }
192 else
193 {
194 csvStr.erase(delStrPos - 1, delStr.size() + 1);
195 }
196 return true;
197 }
198 return false;
199}
200
Patrick Williams9638afb2021-02-22 17:16:24 -0600201bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530202{
203 if (userName.empty())
204 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800205 lg2::error("User name is empty");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530206 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
207 Argument::ARGUMENT_VALUE("Null"));
208 }
209 if (usersList.find(userName) == usersList.end())
210 {
211 return false;
212 }
213 return true;
214}
215
Patrick Williams9638afb2021-02-22 17:16:24 -0600216void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530217{
Nan Zhou8a11d992022-10-25 00:07:06 +0000218 if (!isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530219 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800220 lg2::error("User '{USERNAME}' does not exist", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530221 elog<UserNameDoesNotExist>();
222 }
223}
224
Nan Zhouda401fe2022-10-25 00:07:18 +0000225void UserMgr::checkAndThrowForDisallowedGroupCreation(
226 const std::string& groupName)
227{
228 if (groupName.size() > maxSystemGroupNameLength ||
229 !std::regex_match(groupName.c_str(),
nichanghao.nchd9adc732024-01-17 20:24:17 +0800230 std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
Nan Zhouda401fe2022-10-25 00:07:18 +0000231 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800232 lg2::error("Invalid group name '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000233 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
234 Argument::ARGUMENT_VALUE(groupName.c_str()));
235 }
236 checkAndThrowsForGroupChangeAllowed(groupName);
237}
238
Patrick Williams9638afb2021-02-22 17:16:24 -0600239void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530240{
Nan Zhou8a11d992022-10-25 00:07:06 +0000241 if (isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530242 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800243 lg2::error("User '{USERNAME}' already exists", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530244 elog<UserNameExists>();
245 }
246}
247
248void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600249 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530250{
251 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
252 groupNames.end())
253 {
254 if (userName.length() > ipmiMaxUserNameLen)
255 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800256 lg2::error("User '{USERNAME}' exceeds IPMI username length limit "
257 "({LENGTH} > {LIMIT})",
258 "USERNAME", userName, "LENGTH", userName.length(),
259 "LIMIT", ipmiMaxUserNameLen);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530260 elog<UserNameGroupFail>(
261 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
262 "IPMI length"));
263 }
264 }
265 if (userName.length() > systemMaxUserNameLen)
266 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800267 lg2::error("User '{USERNAME}' exceeds system username length limit "
268 "({LENGTH} > {LIMIT})",
269 "USERNAME", userName, "LENGTH", userName.length(), "LIMIT",
270 systemMaxUserNameLen);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530271 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
272 Argument::ARGUMENT_VALUE("Invalid length"));
273 }
274 if (!std::regex_match(userName.c_str(),
nichanghao.nchd9adc732024-01-17 20:24:17 +0800275 std::regex("[a-zA-Z_][a-zA-Z_0-9]*")))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530276 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800277 lg2::error("Invalid username '{USERNAME}'", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530278 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
279 Argument::ARGUMENT_VALUE("Invalid data"));
280 }
281}
282
Patrick Williams88a82db2025-02-01 08:22:37 -0500283void UserMgr::throwForMaxGrpUserCount(
284 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530285{
286 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
287 groupNames.end())
288 {
289 if (getIpmiUsersCount() >= ipmiMaxUsers)
290 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800291 lg2::error("IPMI user limit reached");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530292 elog<NoResource>(
293 xyz::openbmc_project::User::Common::NoResource::REASON(
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800294 "IPMI user limit reached"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530295 }
296 }
297 else
298 {
299 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
300 (maxSystemUsers - ipmiMaxUsers))
301 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800302 lg2::error("Non-ipmi User limit reached");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530303 elog<NoResource>(
304 xyz::openbmc_project::User::Common::NoResource::REASON(
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800305 "Non-ipmi user limit reached"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530306 }
307 }
308 return;
309}
310
Patrick Williams9638afb2021-02-22 17:16:24 -0600311void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530312{
313 if (!priv.empty() &&
314 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
315 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800316 lg2::error("Invalid privilege '{PRIVILEGE}'", "PRIVILEGE", priv);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530317 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
318 Argument::ARGUMENT_VALUE(priv.c_str()));
319 }
320}
321
Patrick Williams9638afb2021-02-22 17:16:24 -0600322void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530323{
Patrick Williams9638afb2021-02-22 17:16:24 -0600324 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530325 {
326 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
327 groupsMgr.end())
328 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800329 lg2::error("Invalid Group Name '{GROUPNAME}'", "GROUPNAME", group);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530330 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
331 Argument::ARGUMENT_VALUE(group.c_str()));
332 }
333 }
334}
335
Nan Zhouda401fe2022-10-25 00:07:18 +0000336std::vector<std::string> UserMgr::readAllGroupsOnSystem()
337{
338 std::vector<std::string> allGroups = {predefinedGroups.begin(),
339 predefinedGroups.end()};
340 // rewinds to the beginning of the group database
341 setgrent();
342 struct group* gr = getgrent();
343 while (gr != nullptr)
344 {
345 std::string group(gr->gr_name);
346 for (std::string_view prefix : allowedGroupPrefix)
347 {
348 if (group.starts_with(prefix))
349 {
350 allGroups.push_back(gr->gr_name);
351 }
352 }
353 gr = getgrent();
354 }
355 // close the group database
356 endgrent();
357 return allGroups;
358}
359
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530360void UserMgr::createUser(std::string userName,
361 std::vector<std::string> groupNames, std::string priv,
362 bool enabled)
363{
364 throwForInvalidPrivilege(priv);
365 throwForInvalidGroups(groupNames);
366 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500367 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530368 throwForUserExists(userName);
369 throwForUserNameConstraints(userName, groupNames);
370 throwForMaxGrpUserCount(groupNames);
371
372 std::string groups = getCSVFromVector(groupNames);
373 bool sshRequested = removeStringFromCSV(groups, grpSsh);
374
375 // treat privilege as a group - This is to avoid using different file to
376 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530377 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530378 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530379 if (groups.size() != 0)
380 {
381 groups += ",";
382 }
383 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530384 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530385 try
386 {
Nan Zhou49c81362022-10-25 00:07:08 +0000387 executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530388 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600389 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530390 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800391 lg2::error("Unable to create new user '{USERNAME}'", "USERNAME",
392 userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530393 elog<InternalFailure>();
394 }
395
396 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530397 sdbusplus::message::object_path tempObjPath(usersObjPath);
398 tempObjPath /= userName;
399 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530400 std::sort(groupNames.begin(), groupNames.end());
401 usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +0000402 userName, std::make_unique<phosphor::user::Users>(
403 bus, userObj.c_str(), groupNames, priv, enabled, *this));
Abhilash Raju93804eb2024-10-01 00:24:43 -0500404 serializer.store();
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800405 lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530406 return;
407}
408
409void UserMgr::deleteUser(std::string userName)
410{
411 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500412 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530413 throwForUserDoesNotExist(userName);
414 try
415 {
Jayanth Othayothac921a52023-07-21 03:48:55 -0500416 // Clear user fail records
417 executeUserClearFailRecords(userName.c_str());
Patrick Williams4b294622023-08-08 10:19:18 -0500418
419 executeUserDelete(userName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530420 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600421 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530422 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800423 lg2::error("Delete User '{USERNAME}' failed", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530424 elog<InternalFailure>();
425 }
426
427 usersList.erase(userName);
Abhilash Raju93804eb2024-10-01 00:24:43 -0500428 serializer.store();
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800429 lg2::info("User '{USERNAME}' deleted successfully", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530430 return;
431}
432
Nan Zhouda401fe2022-10-25 00:07:18 +0000433void UserMgr::checkDeleteGroupConstraints(const std::string& groupName)
434{
435 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) ==
436 groupsMgr.end())
437 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800438 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000439 elog<GroupNameDoesNotExists>();
440 }
441 checkAndThrowsForGroupChangeAllowed(groupName);
442}
443
444void UserMgr::deleteGroup(std::string groupName)
445{
446 checkDeleteGroupConstraints(groupName);
447 try
448 {
449 executeGroupDeletion(groupName.c_str());
450 }
451 catch (const InternalFailure& e)
452 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800453 lg2::error("Failed to delete group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000454 elog<InternalFailure>();
455 }
456
457 groupsMgr.erase(std::find(groupsMgr.begin(), groupsMgr.end(), groupName));
458 UserMgrIface::allGroups(groupsMgr);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800459 lg2::info("Successfully deleted group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000460}
461
462void UserMgr::checkCreateGroupConstraints(const std::string& groupName)
463{
464 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) !=
465 groupsMgr.end())
466 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800467 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000468 elog<GroupNameExists>();
469 }
470 checkAndThrowForDisallowedGroupCreation(groupName);
471 if (groupsMgr.size() >= maxSystemGroupCount)
472 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800473 lg2::error("Group limit reached");
Nan Zhouda401fe2022-10-25 00:07:18 +0000474 elog<NoResource>(xyz::openbmc_project::User::Common::NoResource::REASON(
475 "Group limit reached"));
476 }
477}
478
479void UserMgr::createGroup(std::string groupName)
480{
481 checkCreateGroupConstraints(groupName);
482 try
483 {
484 executeGroupCreation(groupName.c_str());
485 }
486 catch (const InternalFailure& e)
487 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800488 lg2::error("Failed to create group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000489 elog<InternalFailure>();
490 }
491 groupsMgr.push_back(groupName);
492 UserMgrIface::allGroups(groupsMgr);
493}
494
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530495void UserMgr::renameUser(std::string userName, std::string newUserName)
496{
497 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500498 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530499 throwForUserDoesNotExist(userName);
500 throwForUserExists(newUserName);
501 throwForUserNameConstraints(newUserName,
502 usersList[userName].get()->userGroups());
503 try
504 {
Nan Zhouf25443e2022-10-25 00:07:11 +0000505 executeUserRename(userName.c_str(), newUserName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530506 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600507 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530508 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800509 lg2::error("Rename '{USERNAME}' to '{NEWUSERNAME}' failed", "USERNAME",
510 userName, "NEWUSERNAME", newUserName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530511 elog<InternalFailure>();
512 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600513 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530514 std::string priv = user.get()->userPrivilege();
515 std::vector<std::string> groupNames = user.get()->userGroups();
516 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530517 sdbusplus::message::object_path tempObjPath(usersObjPath);
518 tempObjPath /= newUserName;
519 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530520 // Special group 'ipmi' needs a way to identify user renamed, in order to
521 // update encrypted password. It can't rely only on InterfacesRemoved &
522 // InterfacesAdded. So first send out userRenamed signal.
523 this->userRenamed(userName, newUserName);
524 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000525 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
526 bus, newUserObj.c_str(), groupNames,
527 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530528 return;
529}
530
Patrick Williams9638afb2021-02-22 17:16:24 -0600531void UserMgr::updateGroupsAndPriv(const std::string& userName,
Nan Zhoufef63032022-10-25 00:07:12 +0000532 std::vector<std::string> groupNames,
Patrick Williams9638afb2021-02-22 17:16:24 -0600533 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530534{
535 throwForInvalidPrivilege(priv);
536 throwForInvalidGroups(groupNames);
537 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500538 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530539 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600540 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530541 usersList[userName].get()->userGroups();
542 std::vector<std::string> groupDiff;
543 // Note: already dealing with sorted group lists.
544 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
545 groupNames.begin(), groupNames.end(),
546 std::back_inserter(groupDiff));
547 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
548 groupDiff.end())
549 {
550 throwForUserNameConstraints(userName, groupNames);
551 throwForMaxGrpUserCount(groupNames);
552 }
553
554 std::string groups = getCSVFromVector(groupNames);
555 bool sshRequested = removeStringFromCSV(groups, grpSsh);
556
557 // treat privilege as a group - This is to avoid using different file to
558 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530559 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530560 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530561 if (groups.size() != 0)
562 {
563 groups += ",";
564 }
565 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530566 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530567 try
568 {
Nan Zhoufef63032022-10-25 00:07:12 +0000569 executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530570 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600571 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530572 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800573 lg2::error(
574 "Unable to modify user privilege / groups for user '{USERNAME}'",
575 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530576 elog<InternalFailure>();
577 }
578
Nan Zhoufef63032022-10-25 00:07:12 +0000579 std::sort(groupNames.begin(), groupNames.end());
580 usersList[userName]->setUserGroups(groupNames);
581 usersList[userName]->setUserPrivilege(priv);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800582 lg2::info("User '{USERNAME}' groups / privilege updated successfully",
583 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530584}
585
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530586uint8_t UserMgr::minPasswordLength(uint8_t value)
587{
588 if (value == AccountPolicyIface::minPasswordLength())
589 {
590 return value;
591 }
592 if (value < minPasswdLength)
593 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800594 lg2::error("Attempting to set minPasswordLength to {VALUE}, less than "
595 "{MINVALUE}",
596 "VALUE", value, "MINVALUE", minPasswdLength);
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000597 elog<InvalidArgument>(
598 Argument::ARGUMENT_NAME("minPasswordLength"),
599 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530600 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700601 if (setPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
602 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530603 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800604 lg2::error("Unable to set minPasswordLength to {VALUE}", "VALUE",
605 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530606 elog<InternalFailure>();
607 }
608 return AccountPolicyIface::minPasswordLength(value);
609}
610
611uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
612{
613 if (value == AccountPolicyIface::rememberOldPasswordTimes())
614 {
615 return value;
616 }
Jason M. Bills3b280ec2023-08-15 16:15:48 -0700617 if (setPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
618 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530619 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800620 lg2::error("Unable to set rememberOldPasswordTimes to {VALUE}", "VALUE",
621 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530622 elog<InternalFailure>();
623 }
624 return AccountPolicyIface::rememberOldPasswordTimes(value);
625}
626
627uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
628{
629 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
630 {
631 return value;
632 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700633 if (setPamModuleConfValue(faillockConfigFile, maxFailedAttempt,
634 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530635 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800636 lg2::error("Unable to set maxLoginAttemptBeforeLockout to {VALUE}",
637 "VALUE", value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530638 elog<InternalFailure>();
639 }
640 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
641}
642
643uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
644{
645 if (value == AccountPolicyIface::accountUnlockTimeout())
646 {
647 return value;
648 }
Jason M. Bills2d042d12023-03-28 15:32:45 -0700649 if (setPamModuleConfValue(faillockConfigFile, unlockTimeout,
650 std::to_string(value)) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530651 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800652 lg2::error("Unable to set accountUnlockTimeout to {VALUE}", "VALUE",
653 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530654 elog<InternalFailure>();
655 }
656 return AccountPolicyIface::accountUnlockTimeout(value);
657}
658
Jason M. Bills2d042d12023-03-28 15:32:45 -0700659int UserMgr::getPamModuleConfValue(const std::string& confFile,
660 const std::string& argName,
661 std::string& argValue)
662{
663 std::ifstream fileToRead(confFile, std::ios::in);
664 if (!fileToRead.is_open())
665 {
666 lg2::error("Failed to open pam configuration file {FILENAME}",
667 "FILENAME", confFile);
668 return failure;
669 }
670 std::string line;
671 auto argSearch = argName + "=";
672 size_t startPos = 0;
673 size_t endPos = 0;
674 while (getline(fileToRead, line))
675 {
676 // skip comments section starting with #
677 if ((startPos = line.find('#')) != std::string::npos)
678 {
679 if (startPos == 0)
680 {
681 continue;
682 }
683 // skip comments after meaningful section and process those
684 line = line.substr(0, startPos);
685 }
686 if ((startPos = line.find(argSearch)) != std::string::npos)
687 {
688 if ((endPos = line.find(' ', startPos)) == std::string::npos)
689 {
690 endPos = line.size();
691 }
692 startPos += argSearch.size();
693 argValue = line.substr(startPos, endPos - startPos);
694 return success;
695 }
696 }
697 return failure;
698}
699
Jason M. Bills2d042d12023-03-28 15:32:45 -0700700int UserMgr::setPamModuleConfValue(const std::string& confFile,
701 const std::string& argName,
702 const std::string& argValue)
703{
704 std::string tmpConfFile = confFile + "_tmp";
705 std::ifstream fileToRead(confFile, std::ios::in);
706 std::ofstream fileToWrite(tmpConfFile, std::ios::out);
707 if (!fileToRead.is_open() || !fileToWrite.is_open())
708 {
709 lg2::error("Failed to open pam configuration file {FILENAME}",
710 "FILENAME", confFile);
Jason M. Bills17b88272023-05-22 14:24:02 -0700711 // Delete the unused tmp file
712 std::remove(tmpConfFile.c_str());
Jason M. Bills2d042d12023-03-28 15:32:45 -0700713 return failure;
714 }
715 std::string line;
716 auto argSearch = argName + "=";
717 size_t startPos = 0;
718 size_t endPos = 0;
719 bool found = false;
720 while (getline(fileToRead, line))
721 {
722 // skip comments section starting with #
723 if ((startPos = line.find('#')) != std::string::npos)
724 {
725 if (startPos == 0)
726 {
727 fileToWrite << line << std::endl;
728 continue;
729 }
730 // skip comments after meaningful section and process those
731 line = line.substr(0, startPos);
732 }
733 if ((startPos = line.find(argSearch)) != std::string::npos)
734 {
735 if ((endPos = line.find(' ', startPos)) == std::string::npos)
736 {
737 endPos = line.size();
738 }
739 startPos += argSearch.size();
740 fileToWrite << line.substr(0, startPos) << argValue
741 << line.substr(endPos, line.size() - endPos)
742 << std::endl;
743 found = true;
744 continue;
745 }
746 fileToWrite << line << std::endl;
747 }
748 fileToWrite.close();
749 fileToRead.close();
750 if (found)
751 {
752 if (std::rename(tmpConfFile.c_str(), confFile.c_str()) == 0)
753 {
754 return success;
755 }
756 }
Jason M. Bills17b88272023-05-22 14:24:02 -0700757 // No changes, so delete the unused tmp file
758 std::remove(tmpConfFile.c_str());
Jason M. Bills2d042d12023-03-28 15:32:45 -0700759 return failure;
760}
761
Patrick Williams9638afb2021-02-22 17:16:24 -0600762void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530763{
764 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500765 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530766 throwForUserDoesNotExist(userName);
767 try
768 {
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000769 executeUserModifyUserEnable(userName.c_str(), enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530770 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600771 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530772 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800773 lg2::error("Unable to modify user enabled state for '{USERNAME}'",
774 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530775 elog<InternalFailure>();
776 }
777
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000778 usersList[userName]->setUserEnabled(enabled);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800779 lg2::info("User '{USERNAME}' has been {STATUS}", "USERNAME", userName,
780 "STATUS", enabled ? "Enabled" : "Disabled");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530781}
782
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530783/**
Jason M. Bills2d042d12023-03-28 15:32:45 -0700784 * faillock app will provide the user failed login list with when the attempt
785 * was made, the type, the source, and if it's valid.
786 *
787 * Valid in this case means that the attempt was made within the fail_interval
788 * time. So, we can check this list for the number of valid entries (lines
789 * ending with 'V') compared to the maximum allowed to determine if the user is
790 * locked out.
791 *
792 * This data is only refreshed when an attempt is made, so if the user appears
793 * to be locked out, we must also check if the most recent attempt was older
794 * than the unlock_time to know if the user has since been unlocked.
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530795 **/
Jason M. Bills2d042d12023-03-28 15:32:45 -0700796bool UserMgr::parseFaillockForLockout(
797 const std::vector<std::string>& faillockOutput)
798{
799 uint16_t failAttempts = 0;
800 time_t lastFailedAttempt{};
801 for (const std::string& line : faillockOutput)
802 {
803 if (!line.ends_with("V"))
804 {
805 continue;
806 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530807
Jason M. Bills2d042d12023-03-28 15:32:45 -0700808 // Count this failed attempt
809 failAttempts++;
810
811 // Update the last attempt time
812 // First get the "when" which is the first two words (date and time)
813 size_t pos = line.find(" ");
814 if (pos == std::string::npos)
815 {
816 continue;
817 }
818 pos = line.find(" ", pos + 1);
819 if (pos == std::string::npos)
820 {
821 continue;
822 }
823 std::string failDateTime = line.substr(0, pos);
824
825 // NOTE: Cannot use std::get_time() here as the implementation of %y in
826 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
827 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
828 std::tm tmStruct = {};
829 if (!strptime(failDateTime.c_str(), "%F %T", &tmStruct))
830 {
831 lg2::error("Failed to parse latest failure date/time");
832 elog<InternalFailure>();
833 }
834
835 time_t failTimestamp = std::mktime(&tmStruct);
836 lastFailedAttempt = std::max(failTimestamp, lastFailedAttempt);
837 }
838
839 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
840 {
841 return false;
842 }
843
844 if (lastFailedAttempt +
845 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
846 std::time(NULL))
847 {
848 return false;
849 }
850
851 return true;
852}
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530853
Patrick Williams9638afb2021-02-22 17:16:24 -0600854bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530855{
856 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500857 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800858 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
859 {
860 return false;
861 }
862
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530863 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800864 try
865 {
Nan Zhoua2953032022-11-11 21:50:32 +0000866 output = getFailedAttempt(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800867 }
868 catch (const InternalFailure& e)
869 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800870 lg2::error("Unable to read login failure counter");
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800871 elog<InternalFailure>();
872 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530873
Jason M. Bills2d042d12023-03-28 15:32:45 -0700874 return parseFaillockForLockout(output);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530875}
876
Patrick Williams9638afb2021-02-22 17:16:24 -0600877bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
878 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530879{
880 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500881 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530882 if (value == true)
883 {
884 return userLockedForFailedAttempt(userName);
885 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530886
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800887 try
888 {
Jayanth Othayothac921a52023-07-21 03:48:55 -0500889 // Clear user fail records
890 executeUserClearFailRecords(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800891 }
892 catch (const InternalFailure& e)
893 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800894 lg2::error("Unable to reset login failure counter");
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800895 elog<InternalFailure>();
896 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530897
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530898 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530899}
900
Patrick Williams9638afb2021-02-22 17:16:24 -0600901bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600902{
903 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500904 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600905
Patrick Williams66addf22024-12-18 11:21:29 -0500906 struct spwd spwd{};
Patrick Williams9638afb2021-02-22 17:16:24 -0600907 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600908 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
George Liu34e6ccd2024-09-18 09:22:53 +0800909 if (buflen <= 0)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600910 {
911 // Use a default size if there is no hard limit suggested by sysconf()
912 buflen = 1024;
913 }
914 std::vector<char> buffer(buflen);
Patrick Williams16c2b682024-08-16 15:20:56 -0400915 auto status =
916 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600917 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
918 // If no matching password record was found, these functions return 0
919 // and store NULL in *spwdPtr
920 if ((status == 0) && (&spwd == spwdPtr))
921 {
922 // Determine password validity per "chage" docs, where:
923 // spwd.sp_lstchg == 0 means password is expired, and
924 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000925 constexpr long secondsPerDay = 60 * 60 * 24;
926 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600927 if ((spwd.sp_lstchg == 0) ||
928 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
929 {
930 return true;
931 }
932 }
933 else
934 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000935 // User entry is missing in /etc/shadow, indicating no SHA password.
936 // Treat this as new user without password entry in /etc/shadow
937 // TODO: Add property to indicate user password was not set yet
938 // https://github.com/openbmc/phosphor-user-manager/issues/8
939 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600940 }
941
942 return false;
943}
944
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530945UserSSHLists UserMgr::getUserAndSshGrpList()
946{
947 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500948 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530949
950 std::vector<std::string> userList;
951 std::vector<std::string> sshUsersList;
952 struct passwd pw, *pwp = nullptr;
953 std::array<char, 1024> buffer{};
954
955 phosphor::user::File passwd(passwdFileName, "r");
956 if ((passwd)() == NULL)
957 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800958 lg2::error("Error opening {FILENAME}", "FILENAME", passwdFileName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530959 elog<InternalFailure>();
960 }
961
962 while (true)
963 {
964 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
965 &pwp);
966 if ((r != 0) || (pwp == NULL))
967 {
968 // Any error, break the loop.
969 break;
970 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530971#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530972 // Add all users whose UID >= 1000 and < 65534
973 // and special UID 0.
974 if ((pwp->pw_uid == 0) ||
975 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530976#else
977 // Add all users whose UID >=1000 and < 65534
978 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
979#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530980 {
981 std::string userName(pwp->pw_name);
982 userList.emplace_back(userName);
983
984 // ssh doesn't have separate group. Check login shell entry to
985 // get all users list which are member of ssh group.
986 std::string loginShell(pwp->pw_shell);
987 if (loginShell == "/bin/sh")
988 {
989 sshUsersList.emplace_back(userName);
990 }
991 }
992 }
993 endpwent();
994 return std::make_pair(std::move(userList), std::move(sshUsersList));
995}
996
997size_t UserMgr::getIpmiUsersCount()
998{
999 std::vector<std::string> userList = getUsersInGroup("ipmi");
1000 return userList.size();
1001}
1002
Nan Zhou49c81362022-10-25 00:07:08 +00001003size_t UserMgr::getNonIpmiUsersCount()
1004{
1005 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
1006 return usersList.size() - ipmiUsers.size();
1007}
1008
Patrick Williams9638afb2021-02-22 17:16:24 -06001009bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301010{
1011 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001012 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301013 std::array<char, 4096> buffer{};
1014 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -06001015 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301016 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
1017 buffer.max_size(), &resultPtr);
1018 if (!status && (&spwd == resultPtr))
1019 {
Ivan Moiseevc4183b82024-12-25 11:59:49 +03001020 // according to chage/usermod code -1 means that account does not expire
1021 // https://github.com/shadow-maint/shadow/blob/7a796897e52293efe9e210ab8da32b7aefe65591/src/chage.c
1022 if (resultPtr->sp_expire < 0)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301023 {
Ivan Moiseevc4183b82024-12-25 11:59:49 +03001024 return true;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301025 }
Ivan Moiseevc4183b82024-12-25 11:59:49 +03001026
1027 // check account expiration date against current date
1028 if (resultPtr->sp_expire > currentDate())
1029 {
1030 return true;
1031 }
1032
1033 return false;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301034 }
1035 return false; // assume user is disabled for any error.
1036}
1037
Patrick Williams9638afb2021-02-22 17:16:24 -06001038std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301039{
1040 std::vector<std::string> usersInGroup;
1041 // Should be more than enough to get the pwd structure.
1042 std::array<char, 4096> buffer{};
1043 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -06001044 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301045
1046 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1047 buffer.max_size(), &resultPtr);
1048
1049 if (!status && (&grp == resultPtr))
1050 {
1051 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
1052 {
1053 usersInGroup.emplace_back(*(grp.gr_mem));
1054 }
1055 }
1056 else
1057 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001058 lg2::error("Group '{GROUPNAME}' not found", "GROUPNAME", groupName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301059 // Don't throw error, just return empty userList - fallback
1060 }
1061 return usersInGroup;
1062}
1063
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001064DbusUserObj UserMgr::getPrivilegeMapperObject(void)
1065{
1066 DbusUserObj objects;
1067 try
1068 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001069 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
1070 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001071
Patrick Williams16c2b682024-08-16 15:20:56 -04001072 auto ldapMgmtService =
1073 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001074 auto method = bus.new_method_call(
1075 ldapMgmtService.c_str(), ldapMgrObjBasePath,
1076 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1077
1078 auto reply = bus.call(method);
1079 reply.read(objects);
1080 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001081 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001082 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001083 lg2::error("Unable to get the User Service: {ERR}", "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001084 throw;
1085 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001086 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001087 {
Manojkiran Eda46e773a2024-06-17 14:45:33 +05301088 lg2::error("Failed to execute GetManagedObjects at {PATH}: {ERR}",
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001089 "PATH", ldapMgrObjBasePath, "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001090 throw;
1091 }
1092 return objects;
1093}
1094
Patrick Williams9638afb2021-02-22 17:16:24 -06001095std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001096{
1097 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1098 objMapperInterface, "GetObject");
1099
1100 mapperCall.append(std::move(path));
1101 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1102
1103 auto mapperResponseMsg = bus.call(mapperCall);
1104
1105 if (mapperResponseMsg.is_method_error())
1106 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001107 lg2::error("Error in mapper call");
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001108 elog<InternalFailure>();
1109 }
1110
1111 std::map<std::string, std::vector<std::string>> mapperResponse;
1112 mapperResponseMsg.read(mapperResponse);
1113
1114 if (mapperResponse.begin() == mapperResponse.end())
1115 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001116 lg2::error("Invalid response from mapper");
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001117 elog<InternalFailure>();
1118 }
1119
1120 return mapperResponse.begin()->first;
1121}
1122
Alexander Filippov75626582022-02-09 18:42:37 +03001123gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
1124{
1125 static auto buflen = sysconf(_SC_GETPW_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 passwd pwd;
1133 struct passwd* pwdPtr = nullptr;
1134 std::vector<char> buffer(buflen);
1135
1136 auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(),
1137 buffer.size(), &pwdPtr);
1138 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
1139 // If no matching password record was found, these functions return 0
1140 // and store NULL in *pwdPtr
1141 if (!status && (&pwd == pwdPtr))
1142 {
1143 return pwd.pw_gid;
1144 }
1145
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001146 lg2::error("User {USERNAME} does not exist", "USERNAME", userName);
Alexander Filippov75626582022-02-09 18:42:37 +03001147 elog<UserNameDoesNotExist>();
1148}
1149
1150bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
1151 const std::string& groupName) const
1152{
1153 static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1154 if (buflen <= 0)
1155 {
1156 // Use a default size if there is no hard limit suggested by sysconf()
1157 buflen = 1024;
1158 }
1159
1160 struct group grp;
1161 struct group* grpPtr = nullptr;
1162 std::vector<char> buffer(buflen);
1163
1164 auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1165 buffer.size(), &grpPtr);
1166
1167 // Groups with a lot of members may require a buffer of bigger size than
1168 // suggested by _SC_GETGR_R_SIZE_MAX.
1169 // 32K should be enough for about 2K members.
1170 constexpr auto maxBufferLength = 32 * 1024;
1171 while (status == ERANGE && buflen < maxBufferLength)
1172 {
1173 buflen *= 2;
1174 buffer.resize(buflen);
1175
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001176 lg2::debug("Increase buffer for getgrnam_r() to {SIZE}", "SIZE",
1177 buflen);
Alexander Filippov75626582022-02-09 18:42:37 +03001178
1179 status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1180 buffer.size(), &grpPtr);
1181 }
1182
1183 // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
1184 // If no matching group record was found, these functions return 0
1185 // and store NULL in *grpPtr
1186 if (!status && (&grp == grpPtr))
1187 {
1188 if (primaryGid == grp.gr_gid)
1189 {
1190 return true;
1191 }
1192
1193 for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
1194 {
1195 if (userName == grp.gr_mem[i])
1196 {
1197 return true;
1198 }
1199 }
1200 }
1201 else if (status == ERANGE)
1202 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001203 lg2::error("Group info of {GROUP} requires too much memory", "GROUP",
1204 groupName);
Alexander Filippov75626582022-02-09 18:42:37 +03001205 }
1206 else
1207 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001208 lg2::error("Group {GROUP} does not exist", "GROUP", groupName);
Alexander Filippov75626582022-02-09 18:42:37 +03001209 }
1210
1211 return false;
1212}
1213
Nan Zhouda401fe2022-10-25 00:07:18 +00001214void UserMgr::executeGroupCreation(const char* groupName)
1215{
1216 executeCmd("/usr/sbin/groupadd", groupName);
1217}
1218
1219void UserMgr::executeGroupDeletion(const char* groupName)
1220{
1221 executeCmd("/usr/sbin/groupdel", groupName);
1222}
1223
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001224UserInfoMap UserMgr::getUserInfo(std::string userName)
1225{
1226 UserInfoMap userInfo;
1227 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001228 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001229 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001230 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001231 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1232 userInfo.emplace("UserGroups", user.get()->userGroups());
1233 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1234 userInfo.emplace("UserLockedForFailedAttempt",
1235 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001236 userInfo.emplace("UserPasswordExpired",
1237 user.get()->userPasswordExpired());
Abhilash Rajua1a754c2024-07-25 05:43:40 -05001238 userInfo.emplace("TOTPSecretkeyRequired",
1239 user.get()->secretKeyGenerationRequired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001240 userInfo.emplace("RemoteUser", false);
1241 }
1242 else
1243 {
Alexander Filippov75626582022-02-09 18:42:37 +03001244 auto primaryGid = getPrimaryGroup(userName);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001245
1246 DbusUserObj objects = getPrivilegeMapperObject();
1247
Ravi Teja5fe724a2019-05-07 05:14:42 -05001248 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001249 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001250
1251 try
1252 {
Alexander Filippov75626582022-02-09 18:42:37 +03001253 for (const auto& [path, interfaces] : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001254 {
Alexander Filippov75626582022-02-09 18:42:37 +03001255 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
1256 if (it != interfaces.end())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001257 {
Alexander Filippov75626582022-02-09 18:42:37 +03001258 auto propIt = it->second.find("Enabled");
1259 if (propIt != it->second.end() &&
1260 std::get<bool>(propIt->second))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001261 {
Alexander Filippov75626582022-02-09 18:42:37 +03001262 ldapConfigPath = path.str + '/';
1263 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001264 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001265 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001266 }
1267
1268 if (ldapConfigPath.empty())
1269 {
1270 return userInfo;
1271 }
1272
Alexander Filippov75626582022-02-09 18:42:37 +03001273 for (const auto& [path, interfaces] : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001274 {
Alexander Filippov75626582022-02-09 18:42:37 +03001275 if (!path.str.starts_with(ldapConfigPath))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001276 {
Alexander Filippov75626582022-02-09 18:42:37 +03001277 continue;
1278 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001279
Alexander Filippov75626582022-02-09 18:42:37 +03001280 auto it = interfaces.find(
1281 "xyz.openbmc_project.User.PrivilegeMapperEntry");
1282 if (it != interfaces.end())
1283 {
1284 std::string privilege;
1285 std::string groupName;
1286
1287 for (const auto& [propName, propValue] : it->second)
1288 {
1289 if (propName == "GroupName")
Ravi Teja5fe724a2019-05-07 05:14:42 -05001290 {
Alexander Filippov75626582022-02-09 18:42:37 +03001291 groupName = std::get<std::string>(propValue);
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001292 }
Alexander Filippov75626582022-02-09 18:42:37 +03001293 else if (propName == "Privilege")
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001294 {
Alexander Filippov75626582022-02-09 18:42:37 +03001295 privilege = std::get<std::string>(propValue);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001296 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001297 }
Alexander Filippov75626582022-02-09 18:42:37 +03001298
1299 if (!groupName.empty() && !privilege.empty() &&
1300 isGroupMember(userName, primaryGid, groupName))
1301 {
1302 userPrivilege = privilege;
1303 break;
1304 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001305 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001306 if (!userPrivilege.empty())
1307 {
1308 break;
1309 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001310 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001311
Jiaqing Zhao56862062022-05-31 19:16:09 +08001312 if (!userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001313 {
Jiaqing Zhao56862062022-05-31 19:16:09 +08001314 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001315 }
Jiaqing Zhao56862062022-05-31 19:16:09 +08001316 else
1317 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001318 lg2::warning("LDAP group privilege mapping does not exist, "
1319 "default \"priv-user\" is used");
Jiaqing Zhao56862062022-05-31 19:16:09 +08001320 userInfo.emplace("UserPrivilege", "priv-user");
1321 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001322 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001323 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001324 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001325 lg2::error("Error while accessing variant: {ERR}", "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001326 elog<InternalFailure>();
1327 }
1328 userInfo.emplace("RemoteUser", true);
1329 }
1330
1331 return userInfo;
1332}
1333
Nan Zhou4bc69812022-10-25 00:07:13 +00001334void UserMgr::initializeAccountPolicy()
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301335{
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301336 std::string valueStr;
1337 auto value = minPasswdLength;
1338 unsigned long tmp = 0;
Jason M. Bills2d042d12023-03-28 15:32:45 -07001339 if (getPamModuleConfValue(pwQualityConfigFile, minPasswdLenProp,
1340 valueStr) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301341 {
1342 AccountPolicyIface::minPasswordLength(minPasswdLength);
1343 }
1344 else
1345 {
1346 try
1347 {
1348 tmp = std::stoul(valueStr, nullptr);
1349 if (tmp > std::numeric_limits<decltype(value)>::max())
1350 {
1351 throw std::out_of_range("Out of range");
1352 }
1353 value = static_cast<decltype(value)>(tmp);
1354 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001355 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301356 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001357 lg2::error("Exception for MinPasswordLength: {ERR}", "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001358 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301359 }
1360 AccountPolicyIface::minPasswordLength(value);
1361 }
1362 valueStr.clear();
Jason M. Bills3b280ec2023-08-15 16:15:48 -07001363 if (getPamModuleConfValue(pwHistoryConfigFile, remOldPasswdCount,
1364 valueStr) != success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301365 {
1366 AccountPolicyIface::rememberOldPasswordTimes(0);
1367 }
1368 else
1369 {
1370 value = 0;
1371 try
1372 {
1373 tmp = std::stoul(valueStr, nullptr);
1374 if (tmp > std::numeric_limits<decltype(value)>::max())
1375 {
1376 throw std::out_of_range("Out of range");
1377 }
1378 value = static_cast<decltype(value)>(tmp);
1379 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001380 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301381 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001382 lg2::error("Exception for RememberOldPasswordTimes: {ERR}", "ERR",
1383 e);
Patrick Venture045b1122018-10-16 15:59:29 -07001384 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301385 }
1386 AccountPolicyIface::rememberOldPasswordTimes(value);
1387 }
1388 valueStr.clear();
Jason M. Bills2d042d12023-03-28 15:32:45 -07001389 if (getPamModuleConfValue(faillockConfigFile, maxFailedAttempt, valueStr) !=
1390 success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301391 {
1392 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1393 }
1394 else
1395 {
1396 uint16_t value16 = 0;
1397 try
1398 {
1399 tmp = std::stoul(valueStr, nullptr);
1400 if (tmp > std::numeric_limits<decltype(value16)>::max())
1401 {
1402 throw std::out_of_range("Out of range");
1403 }
1404 value16 = static_cast<decltype(value16)>(tmp);
1405 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001406 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301407 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001408 lg2::error("Exception for MaxLoginAttemptBeforLockout: {ERR}",
1409 "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001410 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301411 }
1412 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1413 }
1414 valueStr.clear();
Jason M. Bills2d042d12023-03-28 15:32:45 -07001415 if (getPamModuleConfValue(faillockConfigFile, unlockTimeout, valueStr) !=
1416 success)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301417 {
1418 AccountPolicyIface::accountUnlockTimeout(0);
1419 }
1420 else
1421 {
1422 uint32_t value32 = 0;
1423 try
1424 {
1425 tmp = std::stoul(valueStr, nullptr);
1426 if (tmp > std::numeric_limits<decltype(value32)>::max())
1427 {
1428 throw std::out_of_range("Out of range");
1429 }
1430 value32 = static_cast<decltype(value32)>(tmp);
1431 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001432 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301433 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001434 lg2::error("Exception for AccountUnlockTimeout: {ERR}", "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001435 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301436 }
1437 AccountPolicyIface::accountUnlockTimeout(value32);
1438 }
Nan Zhou4bc69812022-10-25 00:07:13 +00001439}
1440
1441void UserMgr::initUserObjects(void)
1442{
1443 // All user management lock has to be based on /etc/shadow
1444 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1445 std::vector<std::string> userNameList;
1446 std::vector<std::string> sshGrpUsersList;
1447 UserSSHLists userSSHLists = getUserAndSshGrpList();
1448 userNameList = std::move(userSSHLists.first);
1449 sshGrpUsersList = std::move(userSSHLists.second);
1450
1451 if (!userNameList.empty())
1452 {
1453 std::map<std::string, std::vector<std::string>> groupLists;
Nan Zhouda401fe2022-10-25 00:07:18 +00001454 // We only track users that are in the |predefinedGroups|
1455 // The other groups don't contain real BMC users.
1456 for (const char* grp : predefinedGroups)
Nan Zhou4bc69812022-10-25 00:07:13 +00001457 {
1458 if (grp == grpSsh)
1459 {
1460 groupLists.emplace(grp, sshGrpUsersList);
1461 }
1462 else
1463 {
1464 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1465 groupLists.emplace(grp, grpUsersList);
1466 }
1467 }
1468 for (auto& grp : privMgr)
1469 {
1470 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1471 groupLists.emplace(grp, grpUsersList);
1472 }
1473
1474 for (auto& user : userNameList)
1475 {
1476 std::vector<std::string> userGroups;
1477 std::string userPriv;
1478 for (const auto& grp : groupLists)
1479 {
1480 std::vector<std::string> tempGrp = grp.second;
1481 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1482 tempGrp.end())
1483 {
1484 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1485 privMgr.end())
1486 {
1487 userPriv = grp.first;
1488 }
1489 else
1490 {
1491 userGroups.emplace_back(grp.first);
1492 }
1493 }
1494 }
1495 // Add user objects to the Users path.
1496 sdbusplus::message::object_path tempObjPath(usersObjPath);
1497 tempObjPath /= user;
1498 std::string objPath(tempObjPath);
1499 std::sort(userGroups.begin(), userGroups.end());
1500 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1501 bus, objPath.c_str(), userGroups,
1502 userPriv, isUserEnabled(user), *this));
1503 }
1504 }
1505}
1506
Abhilash Raju93804eb2024-10-01 00:24:43 -05001507void UserMgr::load()
1508{
1509 std::optional<std::string> authTypeStr;
1510 if (std::filesystem::exists(mfaConfPath))
1511 {
1512 serializer.load();
1513 serializer.deserialize("authtype", authTypeStr);
1514 }
1515 auto authType =
1516 authTypeStr.transform(MultiFactorAuthConfiguration::convertStringToType)
1517 .value_or(std::optional(MultiFactorAuthType::None));
1518 if (authType)
1519 {
1520 enabled(*authType, true);
1521 }
1522}
1523
Nan Zhou4bc69812022-10-25 00:07:13 +00001524UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1525 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
Abhilash Raju93804eb2024-10-01 00:24:43 -05001526 serializer(mfaConfPath), faillockConfigFile(defaultFaillockConfigFile),
Jason M. Bills3b280ec2023-08-15 16:15:48 -07001527 pwHistoryConfigFile(defaultPWHistoryConfigFile),
Jason M. Bills2d042d12023-03-28 15:32:45 -07001528 pwQualityConfigFile(defaultPWQualityConfigFile)
Abhilash Raju93804eb2024-10-01 00:24:43 -05001529
Nan Zhou4bc69812022-10-25 00:07:13 +00001530{
1531 UserMgrIface::allPrivileges(privMgr);
Nan Zhouda401fe2022-10-25 00:07:18 +00001532 groupsMgr = readAllGroupsOnSystem();
Nan Zhou4bc69812022-10-25 00:07:13 +00001533 std::sort(groupsMgr.begin(), groupsMgr.end());
1534 UserMgrIface::allGroups(groupsMgr);
1535 initializeAccountPolicy();
Abhilash Raju93804eb2024-10-01 00:24:43 -05001536 load();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301537 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301538 // emit the signal
1539 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301540}
1541
Nan Zhou49c81362022-10-25 00:07:08 +00001542void UserMgr::executeUserAdd(const char* userName, const char* groups,
1543 bool sshRequested, bool enabled)
1544{
1545 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1546 // 1970-01-01, that's an implementation-defined behavior
1547 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001548 (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
Nan Zhou49c81362022-10-25 00:07:08 +00001549 (enabled ? "" : "1970-01-01"));
1550}
1551
1552void UserMgr::executeUserDelete(const char* userName)
1553{
1554 executeCmd("/usr/sbin/userdel", userName, "-r");
1555}
1556
Jayanth Othayothac921a52023-07-21 03:48:55 -05001557void UserMgr::executeUserClearFailRecords(const char* userName)
1558{
1559 executeCmd("/usr/sbin/faillock", "--user", userName, "--reset");
1560}
1561
Nan Zhouf25443e2022-10-25 00:07:11 +00001562void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1563{
1564 std::string newHomeDir = "/home/";
1565 newHomeDir += newUserName;
1566 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1567 newHomeDir.c_str(), "-m");
1568}
1569
Nan Zhoufef63032022-10-25 00:07:12 +00001570void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1571 bool sshRequested)
1572{
1573 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001574 (sshRequested ? "/bin/sh" : "/sbin/nologin"));
Nan Zhoufef63032022-10-25 00:07:12 +00001575}
1576
Nan Zhou6b6f2d82022-10-25 00:07:17 +00001577void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1578{
1579 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1580 // 1970-01-01, that's an implementation-defined behavior
1581 executeCmd("/usr/sbin/usermod", userName, "-e",
1582 (enabled ? "" : "1970-01-01"));
1583}
1584
Nan Zhoua2953032022-11-11 21:50:32 +00001585std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1586{
Jason M. Bills2d042d12023-03-28 15:32:45 -07001587 return executeCmd("/usr/sbin/faillock", "--user", userName);
Nan Zhoua2953032022-11-11 21:50:32 +00001588}
1589
Abhilash Rajua1a754c2024-07-25 05:43:40 -05001590MultiFactorAuthType UserMgr::enabled(MultiFactorAuthType value, bool skipSignal)
1591{
1592 if (value == enabled())
1593 {
1594 return value;
1595 }
1596 switch (value)
1597 {
1598 case MultiFactorAuthType::None:
1599 for (auto type : {MultiFactorAuthType::GoogleAuthenticator})
1600 {
1601 for (auto& u : usersList)
1602 {
1603 u.second->enableMultiFactorAuth(type, false);
1604 }
1605 }
1606 break;
1607 default:
1608 for (auto& u : usersList)
1609 {
1610 u.second->enableMultiFactorAuth(value, true);
1611 }
1612 break;
1613 }
Abhilash Raju93804eb2024-10-01 00:24:43 -05001614 serializer.serialize(
1615 "authtype", MultiFactorAuthConfiguration::convertTypeToString(value));
1616 serializer.store();
Abhilash Rajua1a754c2024-07-25 05:43:40 -05001617 return MultiFactorAuthConfigurationIface::enabled(value, skipSignal);
1618}
1619bool UserMgr::secretKeyRequired(std::string userName)
1620{
1621 if (usersList.contains(userName))
1622 {
1623 return usersList[userName]->secretKeyGenerationRequired();
1624 }
1625 return false;
1626}
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301627} // namespace user
1628} // namespace phosphor