blob: a2b6a76c334fa804f4a08ac578cc448e62d95cb8 [file] [log] [blame]
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16
Patrick Williams9638afb2021-02-22 17:16:24 -060017#include "config.h"
18
19#include "user_mgr.hpp"
20
21#include "file.hpp"
22#include "shadowlock.hpp"
23#include "users.hpp"
24
25#include <grp.h>
26#include <pwd.h>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053027#include <shadow.h>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053028#include <sys/types.h>
29#include <sys/wait.h>
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -060030#include <time.h>
Patrick Williams9638afb2021-02-22 17:16:24 -060031#include <unistd.h>
32
33#include <boost/algorithm/string/split.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -060034#include <phosphor-logging/elog-errors.hpp>
35#include <phosphor-logging/elog.hpp>
Jiaqing Zhao11ec6662022-07-05 20:55:34 +080036#include <phosphor-logging/lg2.hpp>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053037#include <xyz/openbmc_project/Common/error.hpp>
38#include <xyz/openbmc_project/User/Common/error.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -060039
40#include <algorithm>
Nan Zhouda401fe2022-10-25 00:07:18 +000041#include <array>
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +080042#include <ctime>
Patrick Williams9638afb2021-02-22 17:16:24 -060043#include <fstream>
44#include <numeric>
45#include <regex>
Nan Zhoue47c09d2022-10-25 00:06:41 +000046#include <span>
47#include <string>
48#include <string_view>
49#include <vector>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053050
51namespace phosphor
52{
53namespace user
54{
55
Patrick Williams9638afb2021-02-22 17:16:24 -060056static constexpr const char* passwdFileName = "/etc/passwd";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053057static constexpr size_t ipmiMaxUserNameLen = 16;
58static constexpr size_t systemMaxUserNameLen = 30;
Patrick Williams9638afb2021-02-22 17:16:24 -060059static constexpr const char* grpSsh = "ssh";
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +053060static constexpr int success = 0;
61static constexpr int failure = -1;
62
63// pam modules related
Patrick Williams9638afb2021-02-22 17:16:24 -060064static constexpr const char* pamTally2 = "pam_tally2.so";
65static constexpr const char* pamCrackLib = "pam_cracklib.so";
66static constexpr const char* pamPWHistory = "pam_pwhistory.so";
67static constexpr const char* minPasswdLenProp = "minlen";
68static constexpr const char* remOldPasswdCount = "remember";
69static constexpr const char* maxFailedAttempt = "deny";
70static constexpr const char* unlockTimeout = "unlock_time";
Nan Zhoue48085d2022-10-25 00:07:04 +000071static constexpr const char* defaultPamPasswdConfigFile =
72 "/etc/pam.d/common-password";
73static constexpr const char* defaultPamAuthConfigFile =
74 "/etc/pam.d/common-auth";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053075
Ratan Guptaaeaf9412019-02-11 04:41:52 -060076// Object Manager related
Patrick Williams9638afb2021-02-22 17:16:24 -060077static constexpr const char* ldapMgrObjBasePath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060078 "/xyz/openbmc_project/user/ldap";
79
80// Object Mapper related
Patrick Williams9638afb2021-02-22 17:16:24 -060081static constexpr const char* objMapperService =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060082 "xyz.openbmc_project.ObjectMapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060083static constexpr const char* objMapperPath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060084 "/xyz/openbmc_project/object_mapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060085static constexpr const char* objMapperInterface =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060086 "xyz.openbmc_project.ObjectMapper";
87
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053088using namespace phosphor::logging;
89using InsufficientPermission =
90 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
91using InternalFailure =
92 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
93using InvalidArgument =
94 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
95using UserNameExists =
96 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
97using UserNameDoesNotExist =
98 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
99using UserNameGroupFail =
100 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530101using NoResource =
102 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530103using Argument = xyz::openbmc_project::Common::InvalidArgument;
Nan Zhouda401fe2022-10-25 00:07:18 +0000104using GroupNameExists =
105 sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameExists;
106using GroupNameDoesNotExists =
107 sdbusplus::xyz::openbmc_project::User::Common::Error::GroupNameDoesNotExist;
108
109namespace
110{
111
112// The hardcoded groups in OpenBMC projects
113constexpr std::array<const char*, 4> predefinedGroups = {"web", "redfish",
114 "ipmi", "ssh"};
115
116// These prefixes are for Dynamic Redfish authorization. See
117// https://github.com/openbmc/docs/blob/master/designs/redfish-authorization.md
118
119// Base role and base privileges are added by Redfish implementation (e.g.,
120// BMCWeb) at compile time
121constexpr std::array<const char*, 4> allowedGroupPrefix = {
122 "openbmc_rfr_", // OpenBMC Redfish Base Role
123 "openbmc_rfp_", // OpenBMC Redfish Base Privileges
124 "openbmc_orfr_", // OpenBMC Redfish OEM Role
125 "openbmc_orfp_", // OpenBMC Redfish OEM Privileges
126};
127
128void checkAndThrowsForGroupChangeAllowed(const std::string& groupName)
129{
130 bool allowed = false;
131 for (std::string_view prefix : allowedGroupPrefix)
132 {
133 if (groupName.starts_with(prefix))
134 {
135 allowed = true;
136 break;
137 }
138 }
139 if (!allowed)
140 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800141 lg2::error("Group name '{GROUP}' is not in the allowed list", "GROUP",
142 groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000143 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
144 Argument::ARGUMENT_VALUE(groupName.c_str()));
145 }
146}
147
148} // namespace
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530149
Nan Zhoue47c09d2022-10-25 00:06:41 +0000150std::string getCSVFromVector(std::span<const std::string> vec)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530151{
Nan Zhoue47c09d2022-10-25 00:06:41 +0000152 if (vec.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530153 {
Nan Zhoue47c09d2022-10-25 00:06:41 +0000154 return "";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530155 }
Nan Zhoue47c09d2022-10-25 00:06:41 +0000156 return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
157 [](std::string&& val, std::string_view element) {
158 val += ',';
159 val += element;
160 return val;
161 });
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530162}
163
Nan Zhou332fb9d2022-10-25 00:07:03 +0000164bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530165{
166 std::string::size_type delStrPos = csvStr.find(delStr);
167 if (delStrPos != std::string::npos)
168 {
169 // need to also delete the comma char
170 if (delStrPos == 0)
171 {
172 csvStr.erase(delStrPos, delStr.size() + 1);
173 }
174 else
175 {
176 csvStr.erase(delStrPos - 1, delStr.size() + 1);
177 }
178 return true;
179 }
180 return false;
181}
182
Patrick Williams9638afb2021-02-22 17:16:24 -0600183bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530184{
185 if (userName.empty())
186 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800187 lg2::error("User name is empty");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530188 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
189 Argument::ARGUMENT_VALUE("Null"));
190 }
191 if (usersList.find(userName) == usersList.end())
192 {
193 return false;
194 }
195 return true;
196}
197
Patrick Williams9638afb2021-02-22 17:16:24 -0600198void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530199{
Nan Zhou8a11d992022-10-25 00:07:06 +0000200 if (!isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530201 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800202 lg2::error("User '{USERNAME}' does not exist", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530203 elog<UserNameDoesNotExist>();
204 }
205}
206
Nan Zhouda401fe2022-10-25 00:07:18 +0000207void UserMgr::checkAndThrowForDisallowedGroupCreation(
208 const std::string& groupName)
209{
210 if (groupName.size() > maxSystemGroupNameLength ||
211 !std::regex_match(groupName.c_str(),
212 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
213 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800214 lg2::error("Invalid group name '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000215 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Group Name"),
216 Argument::ARGUMENT_VALUE(groupName.c_str()));
217 }
218 checkAndThrowsForGroupChangeAllowed(groupName);
219}
220
Patrick Williams9638afb2021-02-22 17:16:24 -0600221void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530222{
Nan Zhou8a11d992022-10-25 00:07:06 +0000223 if (isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530224 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800225 lg2::error("User '{USERNAME}' already exists", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530226 elog<UserNameExists>();
227 }
228}
229
230void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600231 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530232{
233 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
234 groupNames.end())
235 {
236 if (userName.length() > ipmiMaxUserNameLen)
237 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800238 lg2::error("User '{USERNAME}' exceeds IPMI username length limit "
239 "({LENGTH} > {LIMIT})",
240 "USERNAME", userName, "LENGTH", userName.length(),
241 "LIMIT", ipmiMaxUserNameLen);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530242 elog<UserNameGroupFail>(
243 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
244 "IPMI length"));
245 }
246 }
247 if (userName.length() > systemMaxUserNameLen)
248 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800249 lg2::error("User '{USERNAME}' exceeds system username length limit "
250 "({LENGTH} > {LIMIT})",
251 "USERNAME", userName, "LENGTH", userName.length(), "LIMIT",
252 systemMaxUserNameLen);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530253 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
254 Argument::ARGUMENT_VALUE("Invalid length"));
255 }
256 if (!std::regex_match(userName.c_str(),
257 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
258 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800259 lg2::error("Invalid username '{USERNAME}'", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530260 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
261 Argument::ARGUMENT_VALUE("Invalid data"));
262 }
263}
264
265void UserMgr::throwForMaxGrpUserCount(
Patrick Williams9638afb2021-02-22 17:16:24 -0600266 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530267{
268 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
269 groupNames.end())
270 {
271 if (getIpmiUsersCount() >= ipmiMaxUsers)
272 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800273 lg2::error("IPMI user limit reached");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530274 elog<NoResource>(
275 xyz::openbmc_project::User::Common::NoResource::REASON(
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800276 "IPMI user limit reached"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530277 }
278 }
279 else
280 {
281 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
282 (maxSystemUsers - ipmiMaxUsers))
283 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800284 lg2::error("Non-ipmi User limit reached");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530285 elog<NoResource>(
286 xyz::openbmc_project::User::Common::NoResource::REASON(
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800287 "Non-ipmi user limit reached"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530288 }
289 }
290 return;
291}
292
Patrick Williams9638afb2021-02-22 17:16:24 -0600293void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530294{
295 if (!priv.empty() &&
296 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
297 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800298 lg2::error("Invalid privilege '{PRIVILEGE}'", "PRIVILEGE", priv);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530299 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
300 Argument::ARGUMENT_VALUE(priv.c_str()));
301 }
302}
303
Patrick Williams9638afb2021-02-22 17:16:24 -0600304void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530305{
Patrick Williams9638afb2021-02-22 17:16:24 -0600306 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530307 {
308 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
309 groupsMgr.end())
310 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800311 lg2::error("Invalid Group Name '{GROUPNAME}'", "GROUPNAME", group);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530312 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
313 Argument::ARGUMENT_VALUE(group.c_str()));
314 }
315 }
316}
317
Nan Zhouda401fe2022-10-25 00:07:18 +0000318std::vector<std::string> UserMgr::readAllGroupsOnSystem()
319{
320 std::vector<std::string> allGroups = {predefinedGroups.begin(),
321 predefinedGroups.end()};
322 // rewinds to the beginning of the group database
323 setgrent();
324 struct group* gr = getgrent();
325 while (gr != nullptr)
326 {
327 std::string group(gr->gr_name);
328 for (std::string_view prefix : allowedGroupPrefix)
329 {
330 if (group.starts_with(prefix))
331 {
332 allGroups.push_back(gr->gr_name);
333 }
334 }
335 gr = getgrent();
336 }
337 // close the group database
338 endgrent();
339 return allGroups;
340}
341
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530342void UserMgr::createUser(std::string userName,
343 std::vector<std::string> groupNames, std::string priv,
344 bool enabled)
345{
346 throwForInvalidPrivilege(priv);
347 throwForInvalidGroups(groupNames);
348 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500349 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530350 throwForUserExists(userName);
351 throwForUserNameConstraints(userName, groupNames);
352 throwForMaxGrpUserCount(groupNames);
353
354 std::string groups = getCSVFromVector(groupNames);
355 bool sshRequested = removeStringFromCSV(groups, grpSsh);
356
357 // treat privilege as a group - This is to avoid using different file to
358 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530359 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530360 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530361 if (groups.size() != 0)
362 {
363 groups += ",";
364 }
365 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530366 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530367 try
368 {
Nan Zhou49c81362022-10-25 00:07:08 +0000369 executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530370 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600371 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530372 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800373 lg2::error("Unable to create new user '{USERNAME}'", "USERNAME",
374 userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530375 elog<InternalFailure>();
376 }
377
378 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530379 sdbusplus::message::object_path tempObjPath(usersObjPath);
380 tempObjPath /= userName;
381 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530382 std::sort(groupNames.begin(), groupNames.end());
383 usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +0000384 userName, std::make_unique<phosphor::user::Users>(
385 bus, userObj.c_str(), groupNames, priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530386
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800387 lg2::info("User '{USERNAME}' created successfully", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530388 return;
389}
390
391void UserMgr::deleteUser(std::string userName)
392{
393 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500394 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530395 throwForUserDoesNotExist(userName);
396 try
397 {
Nan Zhou49c81362022-10-25 00:07:08 +0000398 executeUserDelete(userName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530399 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600400 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530401 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800402 lg2::error("Delete User '{USERNAME}' failed", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530403 elog<InternalFailure>();
404 }
405
406 usersList.erase(userName);
407
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800408 lg2::info("User '{USERNAME}' deleted successfully", "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530409 return;
410}
411
Nan Zhouda401fe2022-10-25 00:07:18 +0000412void UserMgr::checkDeleteGroupConstraints(const std::string& groupName)
413{
414 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) ==
415 groupsMgr.end())
416 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800417 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000418 elog<GroupNameDoesNotExists>();
419 }
420 checkAndThrowsForGroupChangeAllowed(groupName);
421}
422
423void UserMgr::deleteGroup(std::string groupName)
424{
425 checkDeleteGroupConstraints(groupName);
426 try
427 {
428 executeGroupDeletion(groupName.c_str());
429 }
430 catch (const InternalFailure& e)
431 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800432 lg2::error("Failed to delete group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000433 elog<InternalFailure>();
434 }
435
436 groupsMgr.erase(std::find(groupsMgr.begin(), groupsMgr.end(), groupName));
437 UserMgrIface::allGroups(groupsMgr);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800438 lg2::info("Successfully deleted group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000439}
440
441void UserMgr::checkCreateGroupConstraints(const std::string& groupName)
442{
443 if (std::find(groupsMgr.begin(), groupsMgr.end(), groupName) !=
444 groupsMgr.end())
445 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800446 lg2::error("Group '{GROUP}' already exists", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000447 elog<GroupNameExists>();
448 }
449 checkAndThrowForDisallowedGroupCreation(groupName);
450 if (groupsMgr.size() >= maxSystemGroupCount)
451 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800452 lg2::error("Group limit reached");
Nan Zhouda401fe2022-10-25 00:07:18 +0000453 elog<NoResource>(xyz::openbmc_project::User::Common::NoResource::REASON(
454 "Group limit reached"));
455 }
456}
457
458void UserMgr::createGroup(std::string groupName)
459{
460 checkCreateGroupConstraints(groupName);
461 try
462 {
463 executeGroupCreation(groupName.c_str());
464 }
465 catch (const InternalFailure& e)
466 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800467 lg2::error("Failed to create group '{GROUP}'", "GROUP", groupName);
Nan Zhouda401fe2022-10-25 00:07:18 +0000468 elog<InternalFailure>();
469 }
470 groupsMgr.push_back(groupName);
471 UserMgrIface::allGroups(groupsMgr);
472}
473
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530474void UserMgr::renameUser(std::string userName, std::string newUserName)
475{
476 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500477 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530478 throwForUserDoesNotExist(userName);
479 throwForUserExists(newUserName);
480 throwForUserNameConstraints(newUserName,
481 usersList[userName].get()->userGroups());
482 try
483 {
Nan Zhouf25443e2022-10-25 00:07:11 +0000484 executeUserRename(userName.c_str(), newUserName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530485 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600486 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530487 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800488 lg2::error("Rename '{USERNAME}' to '{NEWUSERNAME}' failed", "USERNAME",
489 userName, "NEWUSERNAME", newUserName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530490 elog<InternalFailure>();
491 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600492 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530493 std::string priv = user.get()->userPrivilege();
494 std::vector<std::string> groupNames = user.get()->userGroups();
495 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530496 sdbusplus::message::object_path tempObjPath(usersObjPath);
497 tempObjPath /= newUserName;
498 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530499 // Special group 'ipmi' needs a way to identify user renamed, in order to
500 // update encrypted password. It can't rely only on InterfacesRemoved &
501 // InterfacesAdded. So first send out userRenamed signal.
502 this->userRenamed(userName, newUserName);
503 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000504 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
505 bus, newUserObj.c_str(), groupNames,
506 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530507 return;
508}
509
Patrick Williams9638afb2021-02-22 17:16:24 -0600510void UserMgr::updateGroupsAndPriv(const std::string& userName,
Nan Zhoufef63032022-10-25 00:07:12 +0000511 std::vector<std::string> groupNames,
Patrick Williams9638afb2021-02-22 17:16:24 -0600512 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530513{
514 throwForInvalidPrivilege(priv);
515 throwForInvalidGroups(groupNames);
516 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500517 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530518 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600519 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530520 usersList[userName].get()->userGroups();
521 std::vector<std::string> groupDiff;
522 // Note: already dealing with sorted group lists.
523 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
524 groupNames.begin(), groupNames.end(),
525 std::back_inserter(groupDiff));
526 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
527 groupDiff.end())
528 {
529 throwForUserNameConstraints(userName, groupNames);
530 throwForMaxGrpUserCount(groupNames);
531 }
532
533 std::string groups = getCSVFromVector(groupNames);
534 bool sshRequested = removeStringFromCSV(groups, grpSsh);
535
536 // treat privilege as a group - This is to avoid using different file to
537 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530538 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530539 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530540 if (groups.size() != 0)
541 {
542 groups += ",";
543 }
544 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530545 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530546 try
547 {
Nan Zhoufef63032022-10-25 00:07:12 +0000548 executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530549 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600550 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530551 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800552 lg2::error(
553 "Unable to modify user privilege / groups for user '{USERNAME}'",
554 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530555 elog<InternalFailure>();
556 }
557
Nan Zhoufef63032022-10-25 00:07:12 +0000558 std::sort(groupNames.begin(), groupNames.end());
559 usersList[userName]->setUserGroups(groupNames);
560 usersList[userName]->setUserPrivilege(priv);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800561 lg2::info("User '{USERNAME}' groups / privilege updated successfully",
562 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530563}
564
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530565uint8_t UserMgr::minPasswordLength(uint8_t value)
566{
567 if (value == AccountPolicyIface::minPasswordLength())
568 {
569 return value;
570 }
571 if (value < minPasswdLength)
572 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800573 lg2::error("Attempting to set minPasswordLength to {VALUE}, less than "
574 "{MINVALUE}",
575 "VALUE", value, "MINVALUE", minPasswdLength);
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000576 elog<InvalidArgument>(
577 Argument::ARGUMENT_NAME("minPasswordLength"),
578 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530579 }
580 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
581 std::to_string(value)) != success)
582 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800583 lg2::error("Unable to set minPasswordLength to {VALUE}", "VALUE",
584 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530585 elog<InternalFailure>();
586 }
587 return AccountPolicyIface::minPasswordLength(value);
588}
589
590uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
591{
592 if (value == AccountPolicyIface::rememberOldPasswordTimes())
593 {
594 return value;
595 }
596 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
597 std::to_string(value)) != success)
598 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800599 lg2::error("Unable to set rememberOldPasswordTimes to {VALUE}", "VALUE",
600 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530601 elog<InternalFailure>();
602 }
603 return AccountPolicyIface::rememberOldPasswordTimes(value);
604}
605
606uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
607{
608 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
609 {
610 return value;
611 }
612 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
613 std::to_string(value)) != success)
614 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800615 lg2::error("Unable to set maxLoginAttemptBeforeLockout to {VALUE}",
616 "VALUE", value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530617 elog<InternalFailure>();
618 }
619 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
620}
621
622uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
623{
624 if (value == AccountPolicyIface::accountUnlockTimeout())
625 {
626 return value;
627 }
628 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
629 success)
630 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800631 lg2::error("Unable to set accountUnlockTimeout to {VALUE}", "VALUE",
632 value);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530633 elog<InternalFailure>();
634 }
635 return AccountPolicyIface::accountUnlockTimeout(value);
636}
637
Patrick Williams9638afb2021-02-22 17:16:24 -0600638int UserMgr::getPamModuleArgValue(const std::string& moduleName,
639 const std::string& argName,
640 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530641{
642 std::string fileName;
643 if (moduleName == pamTally2)
644 {
645 fileName = pamAuthConfigFile;
646 }
647 else
648 {
649 fileName = pamPasswdConfigFile;
650 }
651 std::ifstream fileToRead(fileName, std::ios::in);
652 if (!fileToRead.is_open())
653 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800654 lg2::error("Failed to open pam configuration file {FILENAME}",
655 "FILENAME", fileName);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530656 return failure;
657 }
658 std::string line;
659 auto argSearch = argName + "=";
660 size_t startPos = 0;
661 size_t endPos = 0;
662 while (getline(fileToRead, line))
663 {
664 // skip comments section starting with #
665 if ((startPos = line.find('#')) != std::string::npos)
666 {
667 if (startPos == 0)
668 {
669 continue;
670 }
671 // skip comments after meaningful section and process those
672 line = line.substr(0, startPos);
673 }
674 if (line.find(moduleName) != std::string::npos)
675 {
676 if ((startPos = line.find(argSearch)) != std::string::npos)
677 {
678 if ((endPos = line.find(' ', startPos)) == std::string::npos)
679 {
680 endPos = line.size();
681 }
682 startPos += argSearch.size();
683 argValue = line.substr(startPos, endPos - startPos);
684 return success;
685 }
686 }
687 }
688 return failure;
689}
690
Patrick Williams9638afb2021-02-22 17:16:24 -0600691int UserMgr::setPamModuleArgValue(const std::string& moduleName,
692 const std::string& argName,
693 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530694{
695 std::string fileName;
696 if (moduleName == pamTally2)
697 {
698 fileName = pamAuthConfigFile;
699 }
700 else
701 {
702 fileName = pamPasswdConfigFile;
703 }
704 std::string tmpFileName = fileName + "_tmp";
705 std::ifstream fileToRead(fileName, std::ios::in);
706 std::ofstream fileToWrite(tmpFileName, std::ios::out);
707 if (!fileToRead.is_open() || !fileToWrite.is_open())
708 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800709 lg2::error("Failed to open pam configuration file {FILENAME}",
710 "FILENAME", fileName);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530711 return failure;
712 }
713 std::string line;
714 auto argSearch = argName + "=";
715 size_t startPos = 0;
716 size_t endPos = 0;
717 bool found = false;
718 while (getline(fileToRead, line))
719 {
720 // skip comments section starting with #
721 if ((startPos = line.find('#')) != std::string::npos)
722 {
723 if (startPos == 0)
724 {
725 fileToWrite << line << std::endl;
726 continue;
727 }
728 // skip comments after meaningful section and process those
729 line = line.substr(0, startPos);
730 }
731 if (line.find(moduleName) != std::string::npos)
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 }
747 fileToWrite << line << std::endl;
748 }
749 fileToWrite.close();
750 fileToRead.close();
751 if (found)
752 {
753 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
754 {
755 return success;
756 }
757 }
758 return failure;
759}
760
Patrick Williams9638afb2021-02-22 17:16:24 -0600761void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530762{
763 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500764 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530765 throwForUserDoesNotExist(userName);
766 try
767 {
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000768 executeUserModifyUserEnable(userName.c_str(), enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530769 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600770 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530771 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800772 lg2::error("Unable to modify user enabled state for '{USERNAME}'",
773 "USERNAME", userName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530774 elog<InternalFailure>();
775 }
776
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000777 usersList[userName]->setUserEnabled(enabled);
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800778 lg2::info("User '{USERNAME}' has been {STATUS}", "USERNAME", userName,
779 "STATUS", enabled ? "Enabled" : "Disabled");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530780}
781
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530782/**
783 * pam_tally2 app will provide the user failure count and failure status
784 * in second line of output with words position [0] - user name,
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800785 * [1] - failure count, [2] - latest failure date, [3] - latest failure time
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530786 * [4] - failure app
787 **/
788
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530789static constexpr size_t t2FailCntIdx = 1;
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800790static constexpr size_t t2FailDateIdx = 2;
791static constexpr size_t t2FailTimeIdx = 3;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530792static constexpr size_t t2OutputIndex = 1;
793
Patrick Williams9638afb2021-02-22 17:16:24 -0600794bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530795{
796 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500797 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800798 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
799 {
800 return false;
801 }
802
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530803 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800804 try
805 {
Nan Zhoua2953032022-11-11 21:50:32 +0000806 output = getFailedAttempt(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800807 }
808 catch (const InternalFailure& e)
809 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800810 lg2::error("Unable to read login failure counter");
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800811 elog<InternalFailure>();
812 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530813
814 std::vector<std::string> splitWords;
815 boost::algorithm::split(splitWords, output[t2OutputIndex],
816 boost::algorithm::is_any_of("\t "),
817 boost::token_compress_on);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800818 uint16_t failAttempts = 0;
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530819 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530820 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530821 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800822 if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530823 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530824 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530825 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800826 failAttempts = static_cast<decltype(failAttempts)>(tmp);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530827 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600828 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530829 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800830 lg2::error("Exception for userLockedForFailedAttempt: {ERR}", "ERR", e);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800831 elog<InternalFailure>();
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530832 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800833
834 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
835 {
836 return false;
837 }
838
839 // When failedAttempts is not 0, Latest failure date/time should be
840 // available
841 if (splitWords.size() < 4)
842 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800843 lg2::error("Unable to read latest failure date/time");
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800844 elog<InternalFailure>();
845 }
846
847 const std::string failDateTime =
848 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
849
850 // NOTE: Cannot use std::get_time() here as the implementation of %y in
851 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
852 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
853 std::tm tmStruct = {};
854 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
855 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800856 lg2::error("Failed to parse latest failure date/time");
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800857 elog<InternalFailure>();
858 }
859
860 time_t failTimestamp = std::mktime(&tmStruct);
861 if (failTimestamp +
862 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
863 std::time(NULL))
864 {
865 return false;
866 }
867
868 return true;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530869}
870
Patrick Williams9638afb2021-02-22 17:16:24 -0600871bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
872 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530873{
874 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500875 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530876 if (value == true)
877 {
878 return userLockedForFailedAttempt(userName);
879 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530880
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800881 try
882 {
883 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
884 }
885 catch (const InternalFailure& e)
886 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800887 lg2::error("Unable to reset login failure counter");
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800888 elog<InternalFailure>();
889 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530890
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530891 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530892}
893
Patrick Williams9638afb2021-02-22 17:16:24 -0600894bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600895{
896 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500897 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600898
899 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600900 {};
901 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600902 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
903 if (buflen < -1)
904 {
905 // Use a default size if there is no hard limit suggested by sysconf()
906 buflen = 1024;
907 }
908 std::vector<char> buffer(buflen);
909 auto status =
910 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
911 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
912 // If no matching password record was found, these functions return 0
913 // and store NULL in *spwdPtr
914 if ((status == 0) && (&spwd == spwdPtr))
915 {
916 // Determine password validity per "chage" docs, where:
917 // spwd.sp_lstchg == 0 means password is expired, and
918 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000919 constexpr long secondsPerDay = 60 * 60 * 24;
920 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600921 if ((spwd.sp_lstchg == 0) ||
922 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
923 {
924 return true;
925 }
926 }
927 else
928 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000929 // User entry is missing in /etc/shadow, indicating no SHA password.
930 // Treat this as new user without password entry in /etc/shadow
931 // TODO: Add property to indicate user password was not set yet
932 // https://github.com/openbmc/phosphor-user-manager/issues/8
933 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600934 }
935
936 return false;
937}
938
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530939UserSSHLists UserMgr::getUserAndSshGrpList()
940{
941 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500942 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530943
944 std::vector<std::string> userList;
945 std::vector<std::string> sshUsersList;
946 struct passwd pw, *pwp = nullptr;
947 std::array<char, 1024> buffer{};
948
949 phosphor::user::File passwd(passwdFileName, "r");
950 if ((passwd)() == NULL)
951 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +0800952 lg2::error("Error opening {FILENAME}", "FILENAME", passwdFileName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530953 elog<InternalFailure>();
954 }
955
956 while (true)
957 {
958 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
959 &pwp);
960 if ((r != 0) || (pwp == NULL))
961 {
962 // Any error, break the loop.
963 break;
964 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530965#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530966 // Add all users whose UID >= 1000 and < 65534
967 // and special UID 0.
968 if ((pwp->pw_uid == 0) ||
969 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530970#else
971 // Add all users whose UID >=1000 and < 65534
972 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
973#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530974 {
975 std::string userName(pwp->pw_name);
976 userList.emplace_back(userName);
977
978 // ssh doesn't have separate group. Check login shell entry to
979 // get all users list which are member of ssh group.
980 std::string loginShell(pwp->pw_shell);
981 if (loginShell == "/bin/sh")
982 {
983 sshUsersList.emplace_back(userName);
984 }
985 }
986 }
987 endpwent();
988 return std::make_pair(std::move(userList), std::move(sshUsersList));
989}
990
991size_t UserMgr::getIpmiUsersCount()
992{
993 std::vector<std::string> userList = getUsersInGroup("ipmi");
994 return userList.size();
995}
996
Nan Zhou49c81362022-10-25 00:07:08 +0000997size_t UserMgr::getNonIpmiUsersCount()
998{
999 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
1000 return usersList.size() - ipmiUsers.size();
1001}
1002
Patrick Williams9638afb2021-02-22 17:16:24 -06001003bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301004{
1005 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001006 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301007 std::array<char, 4096> buffer{};
1008 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -06001009 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301010 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
1011 buffer.max_size(), &resultPtr);
1012 if (!status && (&spwd == resultPtr))
1013 {
1014 if (resultPtr->sp_expire >= 0)
1015 {
1016 return false; // user locked out
1017 }
1018 return true;
1019 }
1020 return false; // assume user is disabled for any error.
1021}
1022
Patrick Williams9638afb2021-02-22 17:16:24 -06001023std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301024{
1025 std::vector<std::string> usersInGroup;
1026 // Should be more than enough to get the pwd structure.
1027 std::array<char, 4096> buffer{};
1028 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -06001029 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301030
1031 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1032 buffer.max_size(), &resultPtr);
1033
1034 if (!status && (&grp == resultPtr))
1035 {
1036 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
1037 {
1038 usersInGroup.emplace_back(*(grp.gr_mem));
1039 }
1040 }
1041 else
1042 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001043 lg2::error("Group '{GROUPNAME}' not found", "GROUPNAME", groupName);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301044 // Don't throw error, just return empty userList - fallback
1045 }
1046 return usersInGroup;
1047}
1048
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001049DbusUserObj UserMgr::getPrivilegeMapperObject(void)
1050{
1051 DbusUserObj objects;
1052 try
1053 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001054 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
1055 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001056
1057 auto ldapMgmtService =
1058 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001059 auto method = bus.new_method_call(
1060 ldapMgmtService.c_str(), ldapMgrObjBasePath,
1061 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1062
1063 auto reply = bus.call(method);
1064 reply.read(objects);
1065 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001066 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001067 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001068 lg2::error("Unable to get the User Service: {ERR}", "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001069 throw;
1070 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001071 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001072 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001073 lg2::error("Failed to excute GetManagedObjects at {PATH}: {ERR}",
1074 "PATH", ldapMgrObjBasePath, "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001075 throw;
1076 }
1077 return objects;
1078}
1079
Patrick Williams9638afb2021-02-22 17:16:24 -06001080std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001081{
1082 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1083 objMapperInterface, "GetObject");
1084
1085 mapperCall.append(std::move(path));
1086 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1087
1088 auto mapperResponseMsg = bus.call(mapperCall);
1089
1090 if (mapperResponseMsg.is_method_error())
1091 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001092 lg2::error("Error in mapper call");
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001093 elog<InternalFailure>();
1094 }
1095
1096 std::map<std::string, std::vector<std::string>> mapperResponse;
1097 mapperResponseMsg.read(mapperResponse);
1098
1099 if (mapperResponse.begin() == mapperResponse.end())
1100 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001101 lg2::error("Invalid response from mapper");
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001102 elog<InternalFailure>();
1103 }
1104
1105 return mapperResponse.begin()->first;
1106}
1107
Alexander Filippov75626582022-02-09 18:42:37 +03001108gid_t UserMgr::getPrimaryGroup(const std::string& userName) const
1109{
1110 static auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
1111 if (buflen <= 0)
1112 {
1113 // Use a default size if there is no hard limit suggested by sysconf()
1114 buflen = 1024;
1115 }
1116
1117 struct passwd pwd;
1118 struct passwd* pwdPtr = nullptr;
1119 std::vector<char> buffer(buflen);
1120
1121 auto status = getpwnam_r(userName.c_str(), &pwd, buffer.data(),
1122 buffer.size(), &pwdPtr);
1123 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
1124 // If no matching password record was found, these functions return 0
1125 // and store NULL in *pwdPtr
1126 if (!status && (&pwd == pwdPtr))
1127 {
1128 return pwd.pw_gid;
1129 }
1130
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001131 lg2::error("User {USERNAME} does not exist", "USERNAME", userName);
Alexander Filippov75626582022-02-09 18:42:37 +03001132 elog<UserNameDoesNotExist>();
1133}
1134
1135bool UserMgr::isGroupMember(const std::string& userName, gid_t primaryGid,
1136 const std::string& groupName) const
1137{
1138 static auto buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
1139 if (buflen <= 0)
1140 {
1141 // Use a default size if there is no hard limit suggested by sysconf()
1142 buflen = 1024;
1143 }
1144
1145 struct group grp;
1146 struct group* grpPtr = nullptr;
1147 std::vector<char> buffer(buflen);
1148
1149 auto status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1150 buffer.size(), &grpPtr);
1151
1152 // Groups with a lot of members may require a buffer of bigger size than
1153 // suggested by _SC_GETGR_R_SIZE_MAX.
1154 // 32K should be enough for about 2K members.
1155 constexpr auto maxBufferLength = 32 * 1024;
1156 while (status == ERANGE && buflen < maxBufferLength)
1157 {
1158 buflen *= 2;
1159 buffer.resize(buflen);
1160
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001161 lg2::debug("Increase buffer for getgrnam_r() to {SIZE}", "SIZE",
1162 buflen);
Alexander Filippov75626582022-02-09 18:42:37 +03001163
1164 status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
1165 buffer.size(), &grpPtr);
1166 }
1167
1168 // On success, getgrnam_r() returns zero, and set *grpPtr to grp.
1169 // If no matching group record was found, these functions return 0
1170 // and store NULL in *grpPtr
1171 if (!status && (&grp == grpPtr))
1172 {
1173 if (primaryGid == grp.gr_gid)
1174 {
1175 return true;
1176 }
1177
1178 for (auto i = 0; grp.gr_mem && grp.gr_mem[i]; ++i)
1179 {
1180 if (userName == grp.gr_mem[i])
1181 {
1182 return true;
1183 }
1184 }
1185 }
1186 else if (status == ERANGE)
1187 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001188 lg2::error("Group info of {GROUP} requires too much memory", "GROUP",
1189 groupName);
Alexander Filippov75626582022-02-09 18:42:37 +03001190 }
1191 else
1192 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001193 lg2::error("Group {GROUP} does not exist", "GROUP", groupName);
Alexander Filippov75626582022-02-09 18:42:37 +03001194 }
1195
1196 return false;
1197}
1198
Nan Zhouda401fe2022-10-25 00:07:18 +00001199void UserMgr::executeGroupCreation(const char* groupName)
1200{
1201 executeCmd("/usr/sbin/groupadd", groupName);
1202}
1203
1204void UserMgr::executeGroupDeletion(const char* groupName)
1205{
1206 executeCmd("/usr/sbin/groupdel", groupName);
1207}
1208
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001209UserInfoMap UserMgr::getUserInfo(std::string userName)
1210{
1211 UserInfoMap userInfo;
1212 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001213 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001214 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001215 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001216 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1217 userInfo.emplace("UserGroups", user.get()->userGroups());
1218 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1219 userInfo.emplace("UserLockedForFailedAttempt",
1220 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001221 userInfo.emplace("UserPasswordExpired",
1222 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001223 userInfo.emplace("RemoteUser", false);
1224 }
1225 else
1226 {
Alexander Filippov75626582022-02-09 18:42:37 +03001227 auto primaryGid = getPrimaryGroup(userName);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001228
1229 DbusUserObj objects = getPrivilegeMapperObject();
1230
Ravi Teja5fe724a2019-05-07 05:14:42 -05001231 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001232 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001233
1234 try
1235 {
Alexander Filippov75626582022-02-09 18:42:37 +03001236 for (const auto& [path, interfaces] : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001237 {
Alexander Filippov75626582022-02-09 18:42:37 +03001238 auto it = interfaces.find("xyz.openbmc_project.Object.Enable");
1239 if (it != interfaces.end())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001240 {
Alexander Filippov75626582022-02-09 18:42:37 +03001241 auto propIt = it->second.find("Enabled");
1242 if (propIt != it->second.end() &&
1243 std::get<bool>(propIt->second))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001244 {
Alexander Filippov75626582022-02-09 18:42:37 +03001245 ldapConfigPath = path.str + '/';
1246 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001247 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001248 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001249 }
1250
1251 if (ldapConfigPath.empty())
1252 {
1253 return userInfo;
1254 }
1255
Alexander Filippov75626582022-02-09 18:42:37 +03001256 for (const auto& [path, interfaces] : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001257 {
Alexander Filippov75626582022-02-09 18:42:37 +03001258 if (!path.str.starts_with(ldapConfigPath))
Ravi Teja5fe724a2019-05-07 05:14:42 -05001259 {
Alexander Filippov75626582022-02-09 18:42:37 +03001260 continue;
1261 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001262
Alexander Filippov75626582022-02-09 18:42:37 +03001263 auto it = interfaces.find(
1264 "xyz.openbmc_project.User.PrivilegeMapperEntry");
1265 if (it != interfaces.end())
1266 {
1267 std::string privilege;
1268 std::string groupName;
1269
1270 for (const auto& [propName, propValue] : it->second)
1271 {
1272 if (propName == "GroupName")
Ravi Teja5fe724a2019-05-07 05:14:42 -05001273 {
Alexander Filippov75626582022-02-09 18:42:37 +03001274 groupName = std::get<std::string>(propValue);
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001275 }
Alexander Filippov75626582022-02-09 18:42:37 +03001276 else if (propName == "Privilege")
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001277 {
Alexander Filippov75626582022-02-09 18:42:37 +03001278 privilege = std::get<std::string>(propValue);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001279 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001280 }
Alexander Filippov75626582022-02-09 18:42:37 +03001281
1282 if (!groupName.empty() && !privilege.empty() &&
1283 isGroupMember(userName, primaryGid, groupName))
1284 {
1285 userPrivilege = privilege;
1286 break;
1287 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001288 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001289 if (!userPrivilege.empty())
1290 {
1291 break;
1292 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001293 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001294
Jiaqing Zhao56862062022-05-31 19:16:09 +08001295 if (!userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001296 {
Jiaqing Zhao56862062022-05-31 19:16:09 +08001297 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001298 }
Jiaqing Zhao56862062022-05-31 19:16:09 +08001299 else
1300 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001301 lg2::warning("LDAP group privilege mapping does not exist, "
1302 "default \"priv-user\" is used");
Jiaqing Zhao56862062022-05-31 19:16:09 +08001303 userInfo.emplace("UserPrivilege", "priv-user");
1304 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001305 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001306 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001307 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001308 lg2::error("Error while accessing variant: {ERR}", "ERR", e);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001309 elog<InternalFailure>();
1310 }
1311 userInfo.emplace("RemoteUser", true);
1312 }
1313
1314 return userInfo;
1315}
1316
Nan Zhou4bc69812022-10-25 00:07:13 +00001317void UserMgr::initializeAccountPolicy()
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301318{
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301319 std::string valueStr;
1320 auto value = minPasswdLength;
1321 unsigned long tmp = 0;
1322 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1323 success)
1324 {
1325 AccountPolicyIface::minPasswordLength(minPasswdLength);
1326 }
1327 else
1328 {
1329 try
1330 {
1331 tmp = std::stoul(valueStr, nullptr);
1332 if (tmp > std::numeric_limits<decltype(value)>::max())
1333 {
1334 throw std::out_of_range("Out of range");
1335 }
1336 value = static_cast<decltype(value)>(tmp);
1337 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001338 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301339 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001340 lg2::error("Exception for MinPasswordLength: {ERR}", "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001341 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301342 }
1343 AccountPolicyIface::minPasswordLength(value);
1344 }
1345 valueStr.clear();
1346 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1347 success)
1348 {
1349 AccountPolicyIface::rememberOldPasswordTimes(0);
1350 }
1351 else
1352 {
1353 value = 0;
1354 try
1355 {
1356 tmp = std::stoul(valueStr, nullptr);
1357 if (tmp > std::numeric_limits<decltype(value)>::max())
1358 {
1359 throw std::out_of_range("Out of range");
1360 }
1361 value = static_cast<decltype(value)>(tmp);
1362 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001363 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301364 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001365 lg2::error("Exception for RememberOldPasswordTimes: {ERR}", "ERR",
1366 e);
Patrick Venture045b1122018-10-16 15:59:29 -07001367 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301368 }
1369 AccountPolicyIface::rememberOldPasswordTimes(value);
1370 }
1371 valueStr.clear();
1372 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1373 {
1374 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1375 }
1376 else
1377 {
1378 uint16_t value16 = 0;
1379 try
1380 {
1381 tmp = std::stoul(valueStr, nullptr);
1382 if (tmp > std::numeric_limits<decltype(value16)>::max())
1383 {
1384 throw std::out_of_range("Out of range");
1385 }
1386 value16 = static_cast<decltype(value16)>(tmp);
1387 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001388 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301389 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001390 lg2::error("Exception for MaxLoginAttemptBeforLockout: {ERR}",
1391 "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001392 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301393 }
1394 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1395 }
1396 valueStr.clear();
1397 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1398 {
1399 AccountPolicyIface::accountUnlockTimeout(0);
1400 }
1401 else
1402 {
1403 uint32_t value32 = 0;
1404 try
1405 {
1406 tmp = std::stoul(valueStr, nullptr);
1407 if (tmp > std::numeric_limits<decltype(value32)>::max())
1408 {
1409 throw std::out_of_range("Out of range");
1410 }
1411 value32 = static_cast<decltype(value32)>(tmp);
1412 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001413 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301414 {
Jiaqing Zhao11ec6662022-07-05 20:55:34 +08001415 lg2::error("Exception for AccountUnlockTimeout: {ERR}", "ERR", e);
Patrick Venture045b1122018-10-16 15:59:29 -07001416 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301417 }
1418 AccountPolicyIface::accountUnlockTimeout(value32);
1419 }
Nan Zhou4bc69812022-10-25 00:07:13 +00001420}
1421
1422void UserMgr::initUserObjects(void)
1423{
1424 // All user management lock has to be based on /etc/shadow
1425 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1426 std::vector<std::string> userNameList;
1427 std::vector<std::string> sshGrpUsersList;
1428 UserSSHLists userSSHLists = getUserAndSshGrpList();
1429 userNameList = std::move(userSSHLists.first);
1430 sshGrpUsersList = std::move(userSSHLists.second);
1431
1432 if (!userNameList.empty())
1433 {
1434 std::map<std::string, std::vector<std::string>> groupLists;
Nan Zhouda401fe2022-10-25 00:07:18 +00001435 // We only track users that are in the |predefinedGroups|
1436 // The other groups don't contain real BMC users.
1437 for (const char* grp : predefinedGroups)
Nan Zhou4bc69812022-10-25 00:07:13 +00001438 {
1439 if (grp == grpSsh)
1440 {
1441 groupLists.emplace(grp, sshGrpUsersList);
1442 }
1443 else
1444 {
1445 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1446 groupLists.emplace(grp, grpUsersList);
1447 }
1448 }
1449 for (auto& grp : privMgr)
1450 {
1451 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1452 groupLists.emplace(grp, grpUsersList);
1453 }
1454
1455 for (auto& user : userNameList)
1456 {
1457 std::vector<std::string> userGroups;
1458 std::string userPriv;
1459 for (const auto& grp : groupLists)
1460 {
1461 std::vector<std::string> tempGrp = grp.second;
1462 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1463 tempGrp.end())
1464 {
1465 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1466 privMgr.end())
1467 {
1468 userPriv = grp.first;
1469 }
1470 else
1471 {
1472 userGroups.emplace_back(grp.first);
1473 }
1474 }
1475 }
1476 // Add user objects to the Users path.
1477 sdbusplus::message::object_path tempObjPath(usersObjPath);
1478 tempObjPath /= user;
1479 std::string objPath(tempObjPath);
1480 std::sort(userGroups.begin(), userGroups.end());
1481 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1482 bus, objPath.c_str(), userGroups,
1483 userPriv, isUserEnabled(user), *this));
1484 }
1485 }
1486}
1487
1488UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1489 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1490 pamPasswdConfigFile(defaultPamPasswdConfigFile),
1491 pamAuthConfigFile(defaultPamAuthConfigFile)
1492{
1493 UserMgrIface::allPrivileges(privMgr);
Nan Zhouda401fe2022-10-25 00:07:18 +00001494 groupsMgr = readAllGroupsOnSystem();
Nan Zhou4bc69812022-10-25 00:07:13 +00001495 std::sort(groupsMgr.begin(), groupsMgr.end());
1496 UserMgrIface::allGroups(groupsMgr);
1497 initializeAccountPolicy();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301498 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301499
1500 // emit the signal
1501 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301502}
1503
Nan Zhou49c81362022-10-25 00:07:08 +00001504void UserMgr::executeUserAdd(const char* userName, const char* groups,
1505 bool sshRequested, bool enabled)
1506{
1507 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1508 // 1970-01-01, that's an implementation-defined behavior
1509 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001510 (sshRequested ? "/bin/sh" : "/sbin/nologin"), "-e",
Nan Zhou49c81362022-10-25 00:07:08 +00001511 (enabled ? "" : "1970-01-01"));
1512}
1513
1514void UserMgr::executeUserDelete(const char* userName)
1515{
1516 executeCmd("/usr/sbin/userdel", userName, "-r");
1517}
1518
Nan Zhouf25443e2022-10-25 00:07:11 +00001519void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1520{
1521 std::string newHomeDir = "/home/";
1522 newHomeDir += newUserName;
1523 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1524 newHomeDir.c_str(), "-m");
1525}
1526
Nan Zhoufef63032022-10-25 00:07:12 +00001527void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1528 bool sshRequested)
1529{
1530 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
Tang Yiwei75ea3e42022-04-25 10:48:15 +08001531 (sshRequested ? "/bin/sh" : "/sbin/nologin"));
Nan Zhoufef63032022-10-25 00:07:12 +00001532}
1533
Nan Zhou6b6f2d82022-10-25 00:07:17 +00001534void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1535{
1536 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1537 // 1970-01-01, that's an implementation-defined behavior
1538 executeCmd("/usr/sbin/usermod", userName, "-e",
1539 (enabled ? "" : "1970-01-01"));
1540}
1541
Nan Zhoua2953032022-11-11 21:50:32 +00001542std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1543{
1544 return executeCmd("/usr/sbin/pam_tally2", "-u", userName);
1545}
1546
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301547} // namespace user
1548} // namespace phosphor