blob: 7bc86b329b8d41e49a256c6ea0ccad525f46b553 [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>
36#include <phosphor-logging/log.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>
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +080041#include <ctime>
Patrick Williams9638afb2021-02-22 17:16:24 -060042#include <fstream>
43#include <numeric>
44#include <regex>
Nan Zhoue47c09d2022-10-25 00:06:41 +000045#include <span>
46#include <string>
47#include <string_view>
48#include <vector>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053049
50namespace phosphor
51{
52namespace user
53{
54
Patrick Williams9638afb2021-02-22 17:16:24 -060055static constexpr const char* passwdFileName = "/etc/passwd";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053056static constexpr size_t ipmiMaxUserNameLen = 16;
57static constexpr size_t systemMaxUserNameLen = 30;
Patrick Williams9638afb2021-02-22 17:16:24 -060058static constexpr const char* grpSsh = "ssh";
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +053059static constexpr uint8_t minPasswdLength = 8;
60static 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;
103
104using Argument = xyz::openbmc_project::Common::InvalidArgument;
105
Nan Zhoue47c09d2022-10-25 00:06:41 +0000106std::string getCSVFromVector(std::span<const std::string> vec)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530107{
Nan Zhoue47c09d2022-10-25 00:06:41 +0000108 if (vec.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530109 {
Nan Zhoue47c09d2022-10-25 00:06:41 +0000110 return "";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530111 }
Nan Zhoue47c09d2022-10-25 00:06:41 +0000112 return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
113 [](std::string&& val, std::string_view element) {
114 val += ',';
115 val += element;
116 return val;
117 });
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530118}
119
Nan Zhou332fb9d2022-10-25 00:07:03 +0000120bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530121{
122 std::string::size_type delStrPos = csvStr.find(delStr);
123 if (delStrPos != std::string::npos)
124 {
125 // need to also delete the comma char
126 if (delStrPos == 0)
127 {
128 csvStr.erase(delStrPos, delStr.size() + 1);
129 }
130 else
131 {
132 csvStr.erase(delStrPos - 1, delStr.size() + 1);
133 }
134 return true;
135 }
136 return false;
137}
138
Patrick Williams9638afb2021-02-22 17:16:24 -0600139bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530140{
141 if (userName.empty())
142 {
143 log<level::ERR>("User name is empty");
144 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
145 Argument::ARGUMENT_VALUE("Null"));
146 }
147 if (usersList.find(userName) == usersList.end())
148 {
149 return false;
150 }
151 return true;
152}
153
Patrick Williams9638afb2021-02-22 17:16:24 -0600154void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530155{
Nan Zhou8a11d992022-10-25 00:07:06 +0000156 if (!isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530157 {
158 log<level::ERR>("User does not exist",
159 entry("USER_NAME=%s", userName.c_str()));
160 elog<UserNameDoesNotExist>();
161 }
162}
163
Patrick Williams9638afb2021-02-22 17:16:24 -0600164void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530165{
Nan Zhou8a11d992022-10-25 00:07:06 +0000166 if (isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530167 {
168 log<level::ERR>("User already exists",
169 entry("USER_NAME=%s", userName.c_str()));
170 elog<UserNameExists>();
171 }
172}
173
174void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600175 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530176{
177 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
178 groupNames.end())
179 {
180 if (userName.length() > ipmiMaxUserNameLen)
181 {
182 log<level::ERR>("IPMI user name length limitation",
183 entry("SIZE=%d", userName.length()));
184 elog<UserNameGroupFail>(
185 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
186 "IPMI length"));
187 }
188 }
189 if (userName.length() > systemMaxUserNameLen)
190 {
191 log<level::ERR>("User name length limitation",
192 entry("SIZE=%d", userName.length()));
193 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
194 Argument::ARGUMENT_VALUE("Invalid length"));
195 }
196 if (!std::regex_match(userName.c_str(),
197 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
198 {
199 log<level::ERR>("Invalid user name",
200 entry("USER_NAME=%s", userName.c_str()));
201 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
202 Argument::ARGUMENT_VALUE("Invalid data"));
203 }
204}
205
206void UserMgr::throwForMaxGrpUserCount(
Patrick Williams9638afb2021-02-22 17:16:24 -0600207 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530208{
209 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
210 groupNames.end())
211 {
212 if (getIpmiUsersCount() >= ipmiMaxUsers)
213 {
214 log<level::ERR>("IPMI user limit reached");
215 elog<NoResource>(
216 xyz::openbmc_project::User::Common::NoResource::REASON(
217 "ipmi user count reached"));
218 }
219 }
220 else
221 {
222 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
223 (maxSystemUsers - ipmiMaxUsers))
224 {
225 log<level::ERR>("Non-ipmi User limit reached");
226 elog<NoResource>(
227 xyz::openbmc_project::User::Common::NoResource::REASON(
228 "Non-ipmi user count reached"));
229 }
230 }
231 return;
232}
233
Patrick Williams9638afb2021-02-22 17:16:24 -0600234void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530235{
236 if (!priv.empty() &&
237 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
238 {
239 log<level::ERR>("Invalid privilege");
240 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
241 Argument::ARGUMENT_VALUE(priv.c_str()));
242 }
243}
244
Patrick Williams9638afb2021-02-22 17:16:24 -0600245void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530246{
Patrick Williams9638afb2021-02-22 17:16:24 -0600247 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530248 {
249 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
250 groupsMgr.end())
251 {
252 log<level::ERR>("Invalid Group Name listed");
253 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
254 Argument::ARGUMENT_VALUE(group.c_str()));
255 }
256 }
257}
258
259void UserMgr::createUser(std::string userName,
260 std::vector<std::string> groupNames, std::string priv,
261 bool enabled)
262{
263 throwForInvalidPrivilege(priv);
264 throwForInvalidGroups(groupNames);
265 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500266 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530267 throwForUserExists(userName);
268 throwForUserNameConstraints(userName, groupNames);
269 throwForMaxGrpUserCount(groupNames);
270
271 std::string groups = getCSVFromVector(groupNames);
272 bool sshRequested = removeStringFromCSV(groups, grpSsh);
273
274 // treat privilege as a group - This is to avoid using different file to
275 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530276 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530277 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530278 if (groups.size() != 0)
279 {
280 groups += ",";
281 }
282 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530283 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530284 try
285 {
Nan Zhou49c81362022-10-25 00:07:08 +0000286 executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530287 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600288 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530289 {
290 log<level::ERR>("Unable to create new user");
291 elog<InternalFailure>();
292 }
293
294 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530295 sdbusplus::message::object_path tempObjPath(usersObjPath);
296 tempObjPath /= userName;
297 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530298 std::sort(groupNames.begin(), groupNames.end());
299 usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +0000300 userName, std::make_unique<phosphor::user::Users>(
301 bus, userObj.c_str(), groupNames, priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530302
303 log<level::INFO>("User created successfully",
304 entry("USER_NAME=%s", userName.c_str()));
305 return;
306}
307
308void UserMgr::deleteUser(std::string userName)
309{
310 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500311 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530312 throwForUserDoesNotExist(userName);
313 try
314 {
Nan Zhou49c81362022-10-25 00:07:08 +0000315 executeUserDelete(userName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530316 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600317 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530318 {
319 log<level::ERR>("User delete failed",
320 entry("USER_NAME=%s", userName.c_str()));
321 elog<InternalFailure>();
322 }
323
324 usersList.erase(userName);
325
326 log<level::INFO>("User deleted successfully",
327 entry("USER_NAME=%s", userName.c_str()));
328 return;
329}
330
331void UserMgr::renameUser(std::string userName, std::string newUserName)
332{
333 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500334 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530335 throwForUserDoesNotExist(userName);
336 throwForUserExists(newUserName);
337 throwForUserNameConstraints(newUserName,
338 usersList[userName].get()->userGroups());
339 try
340 {
Nan Zhouf25443e2022-10-25 00:07:11 +0000341 executeUserRename(userName.c_str(), newUserName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530342 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600343 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530344 {
345 log<level::ERR>("User rename failed",
346 entry("USER_NAME=%s", userName.c_str()));
347 elog<InternalFailure>();
348 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600349 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530350 std::string priv = user.get()->userPrivilege();
351 std::vector<std::string> groupNames = user.get()->userGroups();
352 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530353 sdbusplus::message::object_path tempObjPath(usersObjPath);
354 tempObjPath /= newUserName;
355 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530356 // Special group 'ipmi' needs a way to identify user renamed, in order to
357 // update encrypted password. It can't rely only on InterfacesRemoved &
358 // InterfacesAdded. So first send out userRenamed signal.
359 this->userRenamed(userName, newUserName);
360 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000361 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
362 bus, newUserObj.c_str(), groupNames,
363 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530364 return;
365}
366
Patrick Williams9638afb2021-02-22 17:16:24 -0600367void UserMgr::updateGroupsAndPriv(const std::string& userName,
Nan Zhoufef63032022-10-25 00:07:12 +0000368 std::vector<std::string> groupNames,
Patrick Williams9638afb2021-02-22 17:16:24 -0600369 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530370{
371 throwForInvalidPrivilege(priv);
372 throwForInvalidGroups(groupNames);
373 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500374 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530375 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600376 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530377 usersList[userName].get()->userGroups();
378 std::vector<std::string> groupDiff;
379 // Note: already dealing with sorted group lists.
380 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
381 groupNames.begin(), groupNames.end(),
382 std::back_inserter(groupDiff));
383 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
384 groupDiff.end())
385 {
386 throwForUserNameConstraints(userName, groupNames);
387 throwForMaxGrpUserCount(groupNames);
388 }
389
390 std::string groups = getCSVFromVector(groupNames);
391 bool sshRequested = removeStringFromCSV(groups, grpSsh);
392
393 // treat privilege as a group - This is to avoid using different file to
394 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530395 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530396 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530397 if (groups.size() != 0)
398 {
399 groups += ",";
400 }
401 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530402 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530403 try
404 {
Nan Zhoufef63032022-10-25 00:07:12 +0000405 executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530406 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600407 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530408 {
409 log<level::ERR>("Unable to modify user privilege / groups");
410 elog<InternalFailure>();
411 }
412
413 log<level::INFO>("User groups / privilege updated successfully",
414 entry("USER_NAME=%s", userName.c_str()));
Nan Zhoufef63032022-10-25 00:07:12 +0000415 std::sort(groupNames.begin(), groupNames.end());
416 usersList[userName]->setUserGroups(groupNames);
417 usersList[userName]->setUserPrivilege(priv);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530418 return;
419}
420
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530421uint8_t UserMgr::minPasswordLength(uint8_t value)
422{
423 if (value == AccountPolicyIface::minPasswordLength())
424 {
425 return value;
426 }
427 if (value < minPasswdLength)
428 {
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000429 log<level::ERR>(("Attempting to set minPasswordLength to less than " +
430 std::to_string(minPasswdLength))
431 .c_str(),
432 entry("SIZE=%d", value));
433 elog<InvalidArgument>(
434 Argument::ARGUMENT_NAME("minPasswordLength"),
435 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530436 }
437 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
438 std::to_string(value)) != success)
439 {
440 log<level::ERR>("Unable to set minPasswordLength");
441 elog<InternalFailure>();
442 }
443 return AccountPolicyIface::minPasswordLength(value);
444}
445
446uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
447{
448 if (value == AccountPolicyIface::rememberOldPasswordTimes())
449 {
450 return value;
451 }
452 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
453 std::to_string(value)) != success)
454 {
455 log<level::ERR>("Unable to set rememberOldPasswordTimes");
456 elog<InternalFailure>();
457 }
458 return AccountPolicyIface::rememberOldPasswordTimes(value);
459}
460
461uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
462{
463 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
464 {
465 return value;
466 }
467 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
468 std::to_string(value)) != success)
469 {
470 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
471 elog<InternalFailure>();
472 }
473 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
474}
475
476uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
477{
478 if (value == AccountPolicyIface::accountUnlockTimeout())
479 {
480 return value;
481 }
482 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
483 success)
484 {
485 log<level::ERR>("Unable to set accountUnlockTimeout");
486 elog<InternalFailure>();
487 }
488 return AccountPolicyIface::accountUnlockTimeout(value);
489}
490
Patrick Williams9638afb2021-02-22 17:16:24 -0600491int UserMgr::getPamModuleArgValue(const std::string& moduleName,
492 const std::string& argName,
493 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530494{
495 std::string fileName;
496 if (moduleName == pamTally2)
497 {
498 fileName = pamAuthConfigFile;
499 }
500 else
501 {
502 fileName = pamPasswdConfigFile;
503 }
504 std::ifstream fileToRead(fileName, std::ios::in);
505 if (!fileToRead.is_open())
506 {
507 log<level::ERR>("Failed to open pam configuration file",
508 entry("FILE_NAME=%s", fileName.c_str()));
509 return failure;
510 }
511 std::string line;
512 auto argSearch = argName + "=";
513 size_t startPos = 0;
514 size_t endPos = 0;
515 while (getline(fileToRead, line))
516 {
517 // skip comments section starting with #
518 if ((startPos = line.find('#')) != std::string::npos)
519 {
520 if (startPos == 0)
521 {
522 continue;
523 }
524 // skip comments after meaningful section and process those
525 line = line.substr(0, startPos);
526 }
527 if (line.find(moduleName) != std::string::npos)
528 {
529 if ((startPos = line.find(argSearch)) != std::string::npos)
530 {
531 if ((endPos = line.find(' ', startPos)) == std::string::npos)
532 {
533 endPos = line.size();
534 }
535 startPos += argSearch.size();
536 argValue = line.substr(startPos, endPos - startPos);
537 return success;
538 }
539 }
540 }
541 return failure;
542}
543
Patrick Williams9638afb2021-02-22 17:16:24 -0600544int UserMgr::setPamModuleArgValue(const std::string& moduleName,
545 const std::string& argName,
546 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530547{
548 std::string fileName;
549 if (moduleName == pamTally2)
550 {
551 fileName = pamAuthConfigFile;
552 }
553 else
554 {
555 fileName = pamPasswdConfigFile;
556 }
557 std::string tmpFileName = fileName + "_tmp";
558 std::ifstream fileToRead(fileName, std::ios::in);
559 std::ofstream fileToWrite(tmpFileName, std::ios::out);
560 if (!fileToRead.is_open() || !fileToWrite.is_open())
561 {
562 log<level::ERR>("Failed to open pam configuration /tmp file",
563 entry("FILE_NAME=%s", fileName.c_str()));
564 return failure;
565 }
566 std::string line;
567 auto argSearch = argName + "=";
568 size_t startPos = 0;
569 size_t endPos = 0;
570 bool found = false;
571 while (getline(fileToRead, line))
572 {
573 // skip comments section starting with #
574 if ((startPos = line.find('#')) != std::string::npos)
575 {
576 if (startPos == 0)
577 {
578 fileToWrite << line << std::endl;
579 continue;
580 }
581 // skip comments after meaningful section and process those
582 line = line.substr(0, startPos);
583 }
584 if (line.find(moduleName) != std::string::npos)
585 {
586 if ((startPos = line.find(argSearch)) != std::string::npos)
587 {
588 if ((endPos = line.find(' ', startPos)) == std::string::npos)
589 {
590 endPos = line.size();
591 }
592 startPos += argSearch.size();
593 fileToWrite << line.substr(0, startPos) << argValue
594 << line.substr(endPos, line.size() - endPos)
595 << std::endl;
596 found = true;
597 continue;
598 }
599 }
600 fileToWrite << line << std::endl;
601 }
602 fileToWrite.close();
603 fileToRead.close();
604 if (found)
605 {
606 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
607 {
608 return success;
609 }
610 }
611 return failure;
612}
613
Patrick Williams9638afb2021-02-22 17:16:24 -0600614void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530615{
616 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500617 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530618 throwForUserDoesNotExist(userName);
619 try
620 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800621 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
622 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530623 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800624 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530625 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600626 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530627 {
628 log<level::ERR>("Unable to modify user enabled state");
629 elog<InternalFailure>();
630 }
631
632 log<level::INFO>("User enabled/disabled state updated successfully",
633 entry("USER_NAME=%s", userName.c_str()),
634 entry("ENABLED=%d", enabled));
635 return;
636}
637
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530638/**
639 * pam_tally2 app will provide the user failure count and failure status
640 * in second line of output with words position [0] - user name,
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800641 * [1] - failure count, [2] - latest failure date, [3] - latest failure time
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530642 * [4] - failure app
643 **/
644
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530645static constexpr size_t t2FailCntIdx = 1;
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800646static constexpr size_t t2FailDateIdx = 2;
647static constexpr size_t t2FailTimeIdx = 3;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530648static constexpr size_t t2OutputIndex = 1;
649
Patrick Williams9638afb2021-02-22 17:16:24 -0600650bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530651{
652 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500653 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800654 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
655 {
656 return false;
657 }
658
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530659 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800660 try
661 {
662 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
663 }
664 catch (const InternalFailure& e)
665 {
666 log<level::ERR>("Unable to read login failure counter");
667 elog<InternalFailure>();
668 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530669
670 std::vector<std::string> splitWords;
671 boost::algorithm::split(splitWords, output[t2OutputIndex],
672 boost::algorithm::is_any_of("\t "),
673 boost::token_compress_on);
674
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800675 uint16_t failAttempts = 0;
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530676 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530677 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530678 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800679 if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530680 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530681 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530682 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800683 failAttempts = static_cast<decltype(failAttempts)>(tmp);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530684 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600685 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530686 {
687 log<level::ERR>("Exception for userLockedForFailedAttempt",
688 entry("WHAT=%s", e.what()));
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800689 elog<InternalFailure>();
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530690 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800691
692 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
693 {
694 return false;
695 }
696
697 // When failedAttempts is not 0, Latest failure date/time should be
698 // available
699 if (splitWords.size() < 4)
700 {
701 log<level::ERR>("Unable to read latest failure date/time");
702 elog<InternalFailure>();
703 }
704
705 const std::string failDateTime =
706 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
707
708 // NOTE: Cannot use std::get_time() here as the implementation of %y in
709 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
710 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
711 std::tm tmStruct = {};
712 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
713 {
714 log<level::ERR>("Failed to parse latest failure date/time");
715 elog<InternalFailure>();
716 }
717
718 time_t failTimestamp = std::mktime(&tmStruct);
719 if (failTimestamp +
720 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
721 std::time(NULL))
722 {
723 return false;
724 }
725
726 return true;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530727}
728
Patrick Williams9638afb2021-02-22 17:16:24 -0600729bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
730 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530731{
732 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500733 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530734 if (value == true)
735 {
736 return userLockedForFailedAttempt(userName);
737 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530738
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800739 try
740 {
741 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
742 }
743 catch (const InternalFailure& e)
744 {
745 log<level::ERR>("Unable to reset login failure counter");
746 elog<InternalFailure>();
747 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530748
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530749 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530750}
751
Patrick Williams9638afb2021-02-22 17:16:24 -0600752bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600753{
754 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500755 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600756
757 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600758 {};
759 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600760 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
761 if (buflen < -1)
762 {
763 // Use a default size if there is no hard limit suggested by sysconf()
764 buflen = 1024;
765 }
766 std::vector<char> buffer(buflen);
767 auto status =
768 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
769 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
770 // If no matching password record was found, these functions return 0
771 // and store NULL in *spwdPtr
772 if ((status == 0) && (&spwd == spwdPtr))
773 {
774 // Determine password validity per "chage" docs, where:
775 // spwd.sp_lstchg == 0 means password is expired, and
776 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000777 constexpr long secondsPerDay = 60 * 60 * 24;
778 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600779 if ((spwd.sp_lstchg == 0) ||
780 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
781 {
782 return true;
783 }
784 }
785 else
786 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000787 // User entry is missing in /etc/shadow, indicating no SHA password.
788 // Treat this as new user without password entry in /etc/shadow
789 // TODO: Add property to indicate user password was not set yet
790 // https://github.com/openbmc/phosphor-user-manager/issues/8
791 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600792 }
793
794 return false;
795}
796
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530797UserSSHLists UserMgr::getUserAndSshGrpList()
798{
799 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500800 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530801
802 std::vector<std::string> userList;
803 std::vector<std::string> sshUsersList;
804 struct passwd pw, *pwp = nullptr;
805 std::array<char, 1024> buffer{};
806
807 phosphor::user::File passwd(passwdFileName, "r");
808 if ((passwd)() == NULL)
809 {
810 log<level::ERR>("Error opening the passwd file");
811 elog<InternalFailure>();
812 }
813
814 while (true)
815 {
816 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
817 &pwp);
818 if ((r != 0) || (pwp == NULL))
819 {
820 // Any error, break the loop.
821 break;
822 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530823#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530824 // Add all users whose UID >= 1000 and < 65534
825 // and special UID 0.
826 if ((pwp->pw_uid == 0) ||
827 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530828#else
829 // Add all users whose UID >=1000 and < 65534
830 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
831#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530832 {
833 std::string userName(pwp->pw_name);
834 userList.emplace_back(userName);
835
836 // ssh doesn't have separate group. Check login shell entry to
837 // get all users list which are member of ssh group.
838 std::string loginShell(pwp->pw_shell);
839 if (loginShell == "/bin/sh")
840 {
841 sshUsersList.emplace_back(userName);
842 }
843 }
844 }
845 endpwent();
846 return std::make_pair(std::move(userList), std::move(sshUsersList));
847}
848
849size_t UserMgr::getIpmiUsersCount()
850{
851 std::vector<std::string> userList = getUsersInGroup("ipmi");
852 return userList.size();
853}
854
Nan Zhou49c81362022-10-25 00:07:08 +0000855size_t UserMgr::getNonIpmiUsersCount()
856{
857 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
858 return usersList.size() - ipmiUsers.size();
859}
860
Patrick Williams9638afb2021-02-22 17:16:24 -0600861bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530862{
863 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500864 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530865 std::array<char, 4096> buffer{};
866 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600867 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530868 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
869 buffer.max_size(), &resultPtr);
870 if (!status && (&spwd == resultPtr))
871 {
872 if (resultPtr->sp_expire >= 0)
873 {
874 return false; // user locked out
875 }
876 return true;
877 }
878 return false; // assume user is disabled for any error.
879}
880
Patrick Williams9638afb2021-02-22 17:16:24 -0600881std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530882{
883 std::vector<std::string> usersInGroup;
884 // Should be more than enough to get the pwd structure.
885 std::array<char, 4096> buffer{};
886 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600887 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530888
889 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
890 buffer.max_size(), &resultPtr);
891
892 if (!status && (&grp == resultPtr))
893 {
894 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
895 {
896 usersInGroup.emplace_back(*(grp.gr_mem));
897 }
898 }
899 else
900 {
901 log<level::ERR>("Group not found",
902 entry("GROUP=%s", groupName.c_str()));
903 // Don't throw error, just return empty userList - fallback
904 }
905 return usersInGroup;
906}
907
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600908DbusUserObj UserMgr::getPrivilegeMapperObject(void)
909{
910 DbusUserObj objects;
911 try
912 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500913 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
914 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600915
916 auto ldapMgmtService =
917 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600918 auto method = bus.new_method_call(
919 ldapMgmtService.c_str(), ldapMgrObjBasePath,
920 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
921
922 auto reply = bus.call(method);
923 reply.read(objects);
924 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600925 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600926 {
927 log<level::ERR>("Unable to get the User Service",
928 entry("WHAT=%s", e.what()));
929 throw;
930 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -0500931 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600932 {
933 log<level::ERR>(
934 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
935 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
936 throw;
937 }
938 return objects;
939}
940
Patrick Williams9638afb2021-02-22 17:16:24 -0600941std::string UserMgr::getLdapGroupName(const std::string& userName)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600942{
943 struct passwd pwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600944 {};
945 struct passwd* pwdPtr = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600946 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
947 if (buflen < -1)
948 {
949 // Use a default size if there is no hard limit suggested by sysconf()
950 buflen = 1024;
951 }
952 std::vector<char> buffer(buflen);
953 gid_t gid = 0;
954
955 auto status =
956 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
957 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
958 // If no matching password record was found, these functions return 0
959 // and store NULL in *pwdPtr
960 if (!status && (&pwd == pwdPtr))
961 {
962 gid = pwd.pw_gid;
963 }
964 else
965 {
966 log<level::ERR>("User does not exist",
967 entry("USER_NAME=%s", userName.c_str()));
968 elog<UserNameDoesNotExist>();
969 }
970
Patrick Williams9638afb2021-02-22 17:16:24 -0600971 struct group* groups = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600972 std::string ldapGroupName;
973
974 while ((groups = getgrent()) != NULL)
975 {
976 if (groups->gr_gid == gid)
977 {
978 ldapGroupName = groups->gr_name;
979 break;
980 }
981 }
982 // Call endgrent() to close the group database.
983 endgrent();
984
985 return ldapGroupName;
986}
987
Patrick Williams9638afb2021-02-22 17:16:24 -0600988std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600989{
990 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
991 objMapperInterface, "GetObject");
992
993 mapperCall.append(std::move(path));
994 mapperCall.append(std::vector<std::string>({std::move(intf)}));
995
996 auto mapperResponseMsg = bus.call(mapperCall);
997
998 if (mapperResponseMsg.is_method_error())
999 {
1000 log<level::ERR>("Error in mapper call");
1001 elog<InternalFailure>();
1002 }
1003
1004 std::map<std::string, std::vector<std::string>> mapperResponse;
1005 mapperResponseMsg.read(mapperResponse);
1006
1007 if (mapperResponse.begin() == mapperResponse.end())
1008 {
1009 log<level::ERR>("Invalid response from mapper");
1010 elog<InternalFailure>();
1011 }
1012
1013 return mapperResponse.begin()->first;
1014}
1015
1016UserInfoMap UserMgr::getUserInfo(std::string userName)
1017{
1018 UserInfoMap userInfo;
1019 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001020 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001021 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001022 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001023 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1024 userInfo.emplace("UserGroups", user.get()->userGroups());
1025 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1026 userInfo.emplace("UserLockedForFailedAttempt",
1027 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001028 userInfo.emplace("UserPasswordExpired",
1029 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001030 userInfo.emplace("RemoteUser", false);
1031 }
1032 else
1033 {
1034 std::string ldapGroupName = getLdapGroupName(userName);
1035 if (ldapGroupName.empty())
1036 {
1037 log<level::ERR>("Unable to get group name",
1038 entry("USER_NAME=%s", userName.c_str()));
1039 elog<InternalFailure>();
1040 }
1041
1042 DbusUserObj objects = getPrivilegeMapperObject();
1043
Ravi Teja5fe724a2019-05-07 05:14:42 -05001044 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001045 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001046
1047 try
1048 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001049 for (const auto& obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001050 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001051 for (const auto& interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001052 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001053 if ((interface.first ==
1054 "xyz.openbmc_project.Object.Enable"))
1055 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001056 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001057 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001058 auto value = std::get<bool>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001059 if ((property.first == "Enabled") &&
1060 (value == true))
1061 {
1062 ldapConfigPath = obj.first;
1063 break;
1064 }
1065 }
1066 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001067 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001068 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001069 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001070 break;
1071 }
1072 }
1073
1074 if (ldapConfigPath.empty())
1075 {
1076 return userInfo;
1077 }
1078
Patrick Williams9638afb2021-02-22 17:16:24 -06001079 for (const auto& obj : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001080 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001081 for (const auto& interface : obj.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001082 {
1083 if ((interface.first ==
1084 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1085 (obj.first.str.find(ldapConfigPath) !=
1086 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001087 {
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001088 std::string privilege;
1089 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001090
Patrick Williams9638afb2021-02-22 17:16:24 -06001091 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001092 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001093 auto value = std::get<std::string>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001094 if (property.first == "GroupName")
1095 {
1096 groupName = value;
1097 }
1098 else if (property.first == "Privilege")
1099 {
1100 privilege = value;
1101 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001102 }
1103 if (groupName == ldapGroupName)
1104 {
1105 userPrivilege = privilege;
1106 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001107 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001108 }
1109 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001110 if (!userPrivilege.empty())
1111 {
1112 break;
1113 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001114 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001115
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001116 if (userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001117 {
1118 log<level::ERR>("LDAP group privilege mapping does not exist");
1119 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001120 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001121 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001122 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001123 {
1124 log<level::ERR>("Error while accessing variant",
1125 entry("WHAT=%s", e.what()));
1126 elog<InternalFailure>();
1127 }
1128 userInfo.emplace("RemoteUser", true);
1129 }
1130
1131 return userInfo;
1132}
1133
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301134void UserMgr::initUserObjects(void)
1135{
1136 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001137 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301138 std::vector<std::string> userNameList;
1139 std::vector<std::string> sshGrpUsersList;
1140 UserSSHLists userSSHLists = getUserAndSshGrpList();
1141 userNameList = std::move(userSSHLists.first);
1142 sshGrpUsersList = std::move(userSSHLists.second);
1143
1144 if (!userNameList.empty())
1145 {
1146 std::map<std::string, std::vector<std::string>> groupLists;
Patrick Williams9638afb2021-02-22 17:16:24 -06001147 for (auto& grp : groupsMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301148 {
1149 if (grp == grpSsh)
1150 {
1151 groupLists.emplace(grp, sshGrpUsersList);
1152 }
1153 else
1154 {
1155 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1156 groupLists.emplace(grp, grpUsersList);
1157 }
1158 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001159 for (auto& grp : privMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301160 {
1161 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1162 groupLists.emplace(grp, grpUsersList);
1163 }
1164
Patrick Williams9638afb2021-02-22 17:16:24 -06001165 for (auto& user : userNameList)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301166 {
1167 std::vector<std::string> userGroups;
1168 std::string userPriv;
Patrick Williams9638afb2021-02-22 17:16:24 -06001169 for (const auto& grp : groupLists)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301170 {
1171 std::vector<std::string> tempGrp = grp.second;
1172 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1173 tempGrp.end())
1174 {
1175 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1176 privMgr.end())
1177 {
1178 userPriv = grp.first;
1179 }
1180 else
1181 {
1182 userGroups.emplace_back(grp.first);
1183 }
1184 }
1185 }
1186 // Add user objects to the Users path.
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +05301187 sdbusplus::message::object_path tempObjPath(usersObjPath);
1188 tempObjPath /= user;
1189 std::string objPath(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301190 std::sort(userGroups.begin(), userGroups.end());
Nan Zhou78d85042022-08-29 17:50:22 +00001191 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1192 bus, objPath.c_str(), userGroups,
1193 userPriv, isUserEnabled(user), *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301194 }
1195 }
1196}
1197
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001198UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
Nan Zhoue48085d2022-10-25 00:07:04 +00001199 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1200 pamPasswdConfigFile(defaultPamPasswdConfigFile),
1201 pamAuthConfigFile(defaultPamAuthConfigFile)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301202{
1203 UserMgrIface::allPrivileges(privMgr);
1204 std::sort(groupsMgr.begin(), groupsMgr.end());
1205 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301206 std::string valueStr;
1207 auto value = minPasswdLength;
1208 unsigned long tmp = 0;
1209 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1210 success)
1211 {
1212 AccountPolicyIface::minPasswordLength(minPasswdLength);
1213 }
1214 else
1215 {
1216 try
1217 {
1218 tmp = std::stoul(valueStr, nullptr);
1219 if (tmp > std::numeric_limits<decltype(value)>::max())
1220 {
1221 throw std::out_of_range("Out of range");
1222 }
1223 value = static_cast<decltype(value)>(tmp);
1224 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001225 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301226 {
1227 log<level::ERR>("Exception for MinPasswordLength",
1228 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001229 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301230 }
1231 AccountPolicyIface::minPasswordLength(value);
1232 }
1233 valueStr.clear();
1234 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1235 success)
1236 {
1237 AccountPolicyIface::rememberOldPasswordTimes(0);
1238 }
1239 else
1240 {
1241 value = 0;
1242 try
1243 {
1244 tmp = std::stoul(valueStr, nullptr);
1245 if (tmp > std::numeric_limits<decltype(value)>::max())
1246 {
1247 throw std::out_of_range("Out of range");
1248 }
1249 value = static_cast<decltype(value)>(tmp);
1250 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001251 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301252 {
1253 log<level::ERR>("Exception for RememberOldPasswordTimes",
1254 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001255 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301256 }
1257 AccountPolicyIface::rememberOldPasswordTimes(value);
1258 }
1259 valueStr.clear();
1260 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1261 {
1262 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1263 }
1264 else
1265 {
1266 uint16_t value16 = 0;
1267 try
1268 {
1269 tmp = std::stoul(valueStr, nullptr);
1270 if (tmp > std::numeric_limits<decltype(value16)>::max())
1271 {
1272 throw std::out_of_range("Out of range");
1273 }
1274 value16 = static_cast<decltype(value16)>(tmp);
1275 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001276 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301277 {
1278 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1279 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001280 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301281 }
1282 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1283 }
1284 valueStr.clear();
1285 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1286 {
1287 AccountPolicyIface::accountUnlockTimeout(0);
1288 }
1289 else
1290 {
1291 uint32_t value32 = 0;
1292 try
1293 {
1294 tmp = std::stoul(valueStr, nullptr);
1295 if (tmp > std::numeric_limits<decltype(value32)>::max())
1296 {
1297 throw std::out_of_range("Out of range");
1298 }
1299 value32 = static_cast<decltype(value32)>(tmp);
1300 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001301 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301302 {
1303 log<level::ERR>("Exception for AccountUnlockTimeout",
1304 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001305 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301306 }
1307 AccountPolicyIface::accountUnlockTimeout(value32);
1308 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301309 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301310
1311 // emit the signal
1312 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301313}
1314
Nan Zhou49c81362022-10-25 00:07:08 +00001315void UserMgr::executeUserAdd(const char* userName, const char* groups,
1316 bool sshRequested, bool enabled)
1317{
1318 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1319 // 1970-01-01, that's an implementation-defined behavior
1320 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
1321 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
1322 (enabled ? "" : "1970-01-01"));
1323}
1324
1325void UserMgr::executeUserDelete(const char* userName)
1326{
1327 executeCmd("/usr/sbin/userdel", userName, "-r");
1328}
1329
Nan Zhouf25443e2022-10-25 00:07:11 +00001330void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1331{
1332 std::string newHomeDir = "/home/";
1333 newHomeDir += newUserName;
1334 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1335 newHomeDir.c_str(), "-m");
1336}
1337
Nan Zhoufef63032022-10-25 00:07:12 +00001338void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1339 bool sshRequested)
1340{
1341 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
1342 (sshRequested ? "/bin/sh" : "/bin/nologin"));
1343}
1344
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301345} // namespace user
1346} // namespace phosphor