blob: 8656e6653e94d9d64786db23db931ac06ef14c4e [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,
368 const std::vector<std::string>& groupNames,
369 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 {
405 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
406 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
407 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600408 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530409 {
410 log<level::ERR>("Unable to modify user privilege / groups");
411 elog<InternalFailure>();
412 }
413
414 log<level::INFO>("User groups / privilege updated successfully",
415 entry("USER_NAME=%s", userName.c_str()));
416 return;
417}
418
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530419uint8_t UserMgr::minPasswordLength(uint8_t value)
420{
421 if (value == AccountPolicyIface::minPasswordLength())
422 {
423 return value;
424 }
425 if (value < minPasswdLength)
426 {
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000427 log<level::ERR>(("Attempting to set minPasswordLength to less than " +
428 std::to_string(minPasswdLength))
429 .c_str(),
430 entry("SIZE=%d", value));
431 elog<InvalidArgument>(
432 Argument::ARGUMENT_NAME("minPasswordLength"),
433 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530434 }
435 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
436 std::to_string(value)) != success)
437 {
438 log<level::ERR>("Unable to set minPasswordLength");
439 elog<InternalFailure>();
440 }
441 return AccountPolicyIface::minPasswordLength(value);
442}
443
444uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
445{
446 if (value == AccountPolicyIface::rememberOldPasswordTimes())
447 {
448 return value;
449 }
450 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
451 std::to_string(value)) != success)
452 {
453 log<level::ERR>("Unable to set rememberOldPasswordTimes");
454 elog<InternalFailure>();
455 }
456 return AccountPolicyIface::rememberOldPasswordTimes(value);
457}
458
459uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
460{
461 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
462 {
463 return value;
464 }
465 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
466 std::to_string(value)) != success)
467 {
468 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
469 elog<InternalFailure>();
470 }
471 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
472}
473
474uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
475{
476 if (value == AccountPolicyIface::accountUnlockTimeout())
477 {
478 return value;
479 }
480 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
481 success)
482 {
483 log<level::ERR>("Unable to set accountUnlockTimeout");
484 elog<InternalFailure>();
485 }
486 return AccountPolicyIface::accountUnlockTimeout(value);
487}
488
Patrick Williams9638afb2021-02-22 17:16:24 -0600489int UserMgr::getPamModuleArgValue(const std::string& moduleName,
490 const std::string& argName,
491 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530492{
493 std::string fileName;
494 if (moduleName == pamTally2)
495 {
496 fileName = pamAuthConfigFile;
497 }
498 else
499 {
500 fileName = pamPasswdConfigFile;
501 }
502 std::ifstream fileToRead(fileName, std::ios::in);
503 if (!fileToRead.is_open())
504 {
505 log<level::ERR>("Failed to open pam configuration file",
506 entry("FILE_NAME=%s", fileName.c_str()));
507 return failure;
508 }
509 std::string line;
510 auto argSearch = argName + "=";
511 size_t startPos = 0;
512 size_t endPos = 0;
513 while (getline(fileToRead, line))
514 {
515 // skip comments section starting with #
516 if ((startPos = line.find('#')) != std::string::npos)
517 {
518 if (startPos == 0)
519 {
520 continue;
521 }
522 // skip comments after meaningful section and process those
523 line = line.substr(0, startPos);
524 }
525 if (line.find(moduleName) != std::string::npos)
526 {
527 if ((startPos = line.find(argSearch)) != std::string::npos)
528 {
529 if ((endPos = line.find(' ', startPos)) == std::string::npos)
530 {
531 endPos = line.size();
532 }
533 startPos += argSearch.size();
534 argValue = line.substr(startPos, endPos - startPos);
535 return success;
536 }
537 }
538 }
539 return failure;
540}
541
Patrick Williams9638afb2021-02-22 17:16:24 -0600542int UserMgr::setPamModuleArgValue(const std::string& moduleName,
543 const std::string& argName,
544 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530545{
546 std::string fileName;
547 if (moduleName == pamTally2)
548 {
549 fileName = pamAuthConfigFile;
550 }
551 else
552 {
553 fileName = pamPasswdConfigFile;
554 }
555 std::string tmpFileName = fileName + "_tmp";
556 std::ifstream fileToRead(fileName, std::ios::in);
557 std::ofstream fileToWrite(tmpFileName, std::ios::out);
558 if (!fileToRead.is_open() || !fileToWrite.is_open())
559 {
560 log<level::ERR>("Failed to open pam configuration /tmp file",
561 entry("FILE_NAME=%s", fileName.c_str()));
562 return failure;
563 }
564 std::string line;
565 auto argSearch = argName + "=";
566 size_t startPos = 0;
567 size_t endPos = 0;
568 bool found = false;
569 while (getline(fileToRead, line))
570 {
571 // skip comments section starting with #
572 if ((startPos = line.find('#')) != std::string::npos)
573 {
574 if (startPos == 0)
575 {
576 fileToWrite << line << std::endl;
577 continue;
578 }
579 // skip comments after meaningful section and process those
580 line = line.substr(0, startPos);
581 }
582 if (line.find(moduleName) != std::string::npos)
583 {
584 if ((startPos = line.find(argSearch)) != std::string::npos)
585 {
586 if ((endPos = line.find(' ', startPos)) == std::string::npos)
587 {
588 endPos = line.size();
589 }
590 startPos += argSearch.size();
591 fileToWrite << line.substr(0, startPos) << argValue
592 << line.substr(endPos, line.size() - endPos)
593 << std::endl;
594 found = true;
595 continue;
596 }
597 }
598 fileToWrite << line << std::endl;
599 }
600 fileToWrite.close();
601 fileToRead.close();
602 if (found)
603 {
604 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
605 {
606 return success;
607 }
608 }
609 return failure;
610}
611
Patrick Williams9638afb2021-02-22 17:16:24 -0600612void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530613{
614 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500615 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530616 throwForUserDoesNotExist(userName);
617 try
618 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800619 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
620 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530621 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800622 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530623 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600624 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530625 {
626 log<level::ERR>("Unable to modify user enabled state");
627 elog<InternalFailure>();
628 }
629
630 log<level::INFO>("User enabled/disabled state updated successfully",
631 entry("USER_NAME=%s", userName.c_str()),
632 entry("ENABLED=%d", enabled));
633 return;
634}
635
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530636/**
637 * pam_tally2 app will provide the user failure count and failure status
638 * in second line of output with words position [0] - user name,
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800639 * [1] - failure count, [2] - latest failure date, [3] - latest failure time
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530640 * [4] - failure app
641 **/
642
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530643static constexpr size_t t2FailCntIdx = 1;
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800644static constexpr size_t t2FailDateIdx = 2;
645static constexpr size_t t2FailTimeIdx = 3;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530646static constexpr size_t t2OutputIndex = 1;
647
Patrick Williams9638afb2021-02-22 17:16:24 -0600648bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530649{
650 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500651 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800652 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
653 {
654 return false;
655 }
656
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530657 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800658 try
659 {
660 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
661 }
662 catch (const InternalFailure& e)
663 {
664 log<level::ERR>("Unable to read login failure counter");
665 elog<InternalFailure>();
666 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530667
668 std::vector<std::string> splitWords;
669 boost::algorithm::split(splitWords, output[t2OutputIndex],
670 boost::algorithm::is_any_of("\t "),
671 boost::token_compress_on);
672
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800673 uint16_t failAttempts = 0;
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530674 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530675 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530676 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800677 if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530678 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530679 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530680 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800681 failAttempts = static_cast<decltype(failAttempts)>(tmp);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530682 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600683 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530684 {
685 log<level::ERR>("Exception for userLockedForFailedAttempt",
686 entry("WHAT=%s", e.what()));
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800687 elog<InternalFailure>();
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530688 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800689
690 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
691 {
692 return false;
693 }
694
695 // When failedAttempts is not 0, Latest failure date/time should be
696 // available
697 if (splitWords.size() < 4)
698 {
699 log<level::ERR>("Unable to read latest failure date/time");
700 elog<InternalFailure>();
701 }
702
703 const std::string failDateTime =
704 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
705
706 // NOTE: Cannot use std::get_time() here as the implementation of %y in
707 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
708 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
709 std::tm tmStruct = {};
710 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
711 {
712 log<level::ERR>("Failed to parse latest failure date/time");
713 elog<InternalFailure>();
714 }
715
716 time_t failTimestamp = std::mktime(&tmStruct);
717 if (failTimestamp +
718 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
719 std::time(NULL))
720 {
721 return false;
722 }
723
724 return true;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530725}
726
Patrick Williams9638afb2021-02-22 17:16:24 -0600727bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
728 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530729{
730 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500731 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530732 if (value == true)
733 {
734 return userLockedForFailedAttempt(userName);
735 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530736
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800737 try
738 {
739 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
740 }
741 catch (const InternalFailure& e)
742 {
743 log<level::ERR>("Unable to reset login failure counter");
744 elog<InternalFailure>();
745 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530746
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530747 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530748}
749
Patrick Williams9638afb2021-02-22 17:16:24 -0600750bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600751{
752 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500753 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600754
755 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600756 {};
757 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600758 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
759 if (buflen < -1)
760 {
761 // Use a default size if there is no hard limit suggested by sysconf()
762 buflen = 1024;
763 }
764 std::vector<char> buffer(buflen);
765 auto status =
766 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
767 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
768 // If no matching password record was found, these functions return 0
769 // and store NULL in *spwdPtr
770 if ((status == 0) && (&spwd == spwdPtr))
771 {
772 // Determine password validity per "chage" docs, where:
773 // spwd.sp_lstchg == 0 means password is expired, and
774 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000775 constexpr long secondsPerDay = 60 * 60 * 24;
776 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600777 if ((spwd.sp_lstchg == 0) ||
778 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
779 {
780 return true;
781 }
782 }
783 else
784 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000785 // User entry is missing in /etc/shadow, indicating no SHA password.
786 // Treat this as new user without password entry in /etc/shadow
787 // TODO: Add property to indicate user password was not set yet
788 // https://github.com/openbmc/phosphor-user-manager/issues/8
789 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600790 }
791
792 return false;
793}
794
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530795UserSSHLists UserMgr::getUserAndSshGrpList()
796{
797 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500798 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530799
800 std::vector<std::string> userList;
801 std::vector<std::string> sshUsersList;
802 struct passwd pw, *pwp = nullptr;
803 std::array<char, 1024> buffer{};
804
805 phosphor::user::File passwd(passwdFileName, "r");
806 if ((passwd)() == NULL)
807 {
808 log<level::ERR>("Error opening the passwd file");
809 elog<InternalFailure>();
810 }
811
812 while (true)
813 {
814 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
815 &pwp);
816 if ((r != 0) || (pwp == NULL))
817 {
818 // Any error, break the loop.
819 break;
820 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530821#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530822 // Add all users whose UID >= 1000 and < 65534
823 // and special UID 0.
824 if ((pwp->pw_uid == 0) ||
825 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530826#else
827 // Add all users whose UID >=1000 and < 65534
828 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
829#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530830 {
831 std::string userName(pwp->pw_name);
832 userList.emplace_back(userName);
833
834 // ssh doesn't have separate group. Check login shell entry to
835 // get all users list which are member of ssh group.
836 std::string loginShell(pwp->pw_shell);
837 if (loginShell == "/bin/sh")
838 {
839 sshUsersList.emplace_back(userName);
840 }
841 }
842 }
843 endpwent();
844 return std::make_pair(std::move(userList), std::move(sshUsersList));
845}
846
847size_t UserMgr::getIpmiUsersCount()
848{
849 std::vector<std::string> userList = getUsersInGroup("ipmi");
850 return userList.size();
851}
852
Nan Zhou49c81362022-10-25 00:07:08 +0000853size_t UserMgr::getNonIpmiUsersCount()
854{
855 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
856 return usersList.size() - ipmiUsers.size();
857}
858
Patrick Williams9638afb2021-02-22 17:16:24 -0600859bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530860{
861 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500862 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530863 std::array<char, 4096> buffer{};
864 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600865 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530866 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
867 buffer.max_size(), &resultPtr);
868 if (!status && (&spwd == resultPtr))
869 {
870 if (resultPtr->sp_expire >= 0)
871 {
872 return false; // user locked out
873 }
874 return true;
875 }
876 return false; // assume user is disabled for any error.
877}
878
Patrick Williams9638afb2021-02-22 17:16:24 -0600879std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530880{
881 std::vector<std::string> usersInGroup;
882 // Should be more than enough to get the pwd structure.
883 std::array<char, 4096> buffer{};
884 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600885 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530886
887 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
888 buffer.max_size(), &resultPtr);
889
890 if (!status && (&grp == resultPtr))
891 {
892 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
893 {
894 usersInGroup.emplace_back(*(grp.gr_mem));
895 }
896 }
897 else
898 {
899 log<level::ERR>("Group not found",
900 entry("GROUP=%s", groupName.c_str()));
901 // Don't throw error, just return empty userList - fallback
902 }
903 return usersInGroup;
904}
905
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600906DbusUserObj UserMgr::getPrivilegeMapperObject(void)
907{
908 DbusUserObj objects;
909 try
910 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500911 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
912 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600913
914 auto ldapMgmtService =
915 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600916 auto method = bus.new_method_call(
917 ldapMgmtService.c_str(), ldapMgrObjBasePath,
918 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
919
920 auto reply = bus.call(method);
921 reply.read(objects);
922 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600923 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600924 {
925 log<level::ERR>("Unable to get the User Service",
926 entry("WHAT=%s", e.what()));
927 throw;
928 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -0500929 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600930 {
931 log<level::ERR>(
932 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
933 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
934 throw;
935 }
936 return objects;
937}
938
Patrick Williams9638afb2021-02-22 17:16:24 -0600939std::string UserMgr::getLdapGroupName(const std::string& userName)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600940{
941 struct passwd pwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600942 {};
943 struct passwd* pwdPtr = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600944 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
945 if (buflen < -1)
946 {
947 // Use a default size if there is no hard limit suggested by sysconf()
948 buflen = 1024;
949 }
950 std::vector<char> buffer(buflen);
951 gid_t gid = 0;
952
953 auto status =
954 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
955 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
956 // If no matching password record was found, these functions return 0
957 // and store NULL in *pwdPtr
958 if (!status && (&pwd == pwdPtr))
959 {
960 gid = pwd.pw_gid;
961 }
962 else
963 {
964 log<level::ERR>("User does not exist",
965 entry("USER_NAME=%s", userName.c_str()));
966 elog<UserNameDoesNotExist>();
967 }
968
Patrick Williams9638afb2021-02-22 17:16:24 -0600969 struct group* groups = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600970 std::string ldapGroupName;
971
972 while ((groups = getgrent()) != NULL)
973 {
974 if (groups->gr_gid == gid)
975 {
976 ldapGroupName = groups->gr_name;
977 break;
978 }
979 }
980 // Call endgrent() to close the group database.
981 endgrent();
982
983 return ldapGroupName;
984}
985
Patrick Williams9638afb2021-02-22 17:16:24 -0600986std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600987{
988 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
989 objMapperInterface, "GetObject");
990
991 mapperCall.append(std::move(path));
992 mapperCall.append(std::vector<std::string>({std::move(intf)}));
993
994 auto mapperResponseMsg = bus.call(mapperCall);
995
996 if (mapperResponseMsg.is_method_error())
997 {
998 log<level::ERR>("Error in mapper call");
999 elog<InternalFailure>();
1000 }
1001
1002 std::map<std::string, std::vector<std::string>> mapperResponse;
1003 mapperResponseMsg.read(mapperResponse);
1004
1005 if (mapperResponse.begin() == mapperResponse.end())
1006 {
1007 log<level::ERR>("Invalid response from mapper");
1008 elog<InternalFailure>();
1009 }
1010
1011 return mapperResponse.begin()->first;
1012}
1013
1014UserInfoMap UserMgr::getUserInfo(std::string userName)
1015{
1016 UserInfoMap userInfo;
1017 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001018 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001019 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001020 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001021 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1022 userInfo.emplace("UserGroups", user.get()->userGroups());
1023 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1024 userInfo.emplace("UserLockedForFailedAttempt",
1025 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001026 userInfo.emplace("UserPasswordExpired",
1027 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001028 userInfo.emplace("RemoteUser", false);
1029 }
1030 else
1031 {
1032 std::string ldapGroupName = getLdapGroupName(userName);
1033 if (ldapGroupName.empty())
1034 {
1035 log<level::ERR>("Unable to get group name",
1036 entry("USER_NAME=%s", userName.c_str()));
1037 elog<InternalFailure>();
1038 }
1039
1040 DbusUserObj objects = getPrivilegeMapperObject();
1041
Ravi Teja5fe724a2019-05-07 05:14:42 -05001042 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001043 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001044
1045 try
1046 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001047 for (const auto& obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001048 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001049 for (const auto& interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001050 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001051 if ((interface.first ==
1052 "xyz.openbmc_project.Object.Enable"))
1053 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001054 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001055 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001056 auto value = std::get<bool>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001057 if ((property.first == "Enabled") &&
1058 (value == true))
1059 {
1060 ldapConfigPath = obj.first;
1061 break;
1062 }
1063 }
1064 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001065 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001066 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001067 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001068 break;
1069 }
1070 }
1071
1072 if (ldapConfigPath.empty())
1073 {
1074 return userInfo;
1075 }
1076
Patrick Williams9638afb2021-02-22 17:16:24 -06001077 for (const auto& obj : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001078 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001079 for (const auto& interface : obj.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001080 {
1081 if ((interface.first ==
1082 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1083 (obj.first.str.find(ldapConfigPath) !=
1084 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001085 {
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001086 std::string privilege;
1087 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001088
Patrick Williams9638afb2021-02-22 17:16:24 -06001089 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001090 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001091 auto value = std::get<std::string>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001092 if (property.first == "GroupName")
1093 {
1094 groupName = value;
1095 }
1096 else if (property.first == "Privilege")
1097 {
1098 privilege = value;
1099 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001100 }
1101 if (groupName == ldapGroupName)
1102 {
1103 userPrivilege = privilege;
1104 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001105 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001106 }
1107 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001108 if (!userPrivilege.empty())
1109 {
1110 break;
1111 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001112 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001113
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001114 if (userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001115 {
1116 log<level::ERR>("LDAP group privilege mapping does not exist");
1117 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001118 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001119 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001120 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001121 {
1122 log<level::ERR>("Error while accessing variant",
1123 entry("WHAT=%s", e.what()));
1124 elog<InternalFailure>();
1125 }
1126 userInfo.emplace("RemoteUser", true);
1127 }
1128
1129 return userInfo;
1130}
1131
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301132void UserMgr::initUserObjects(void)
1133{
1134 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001135 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301136 std::vector<std::string> userNameList;
1137 std::vector<std::string> sshGrpUsersList;
1138 UserSSHLists userSSHLists = getUserAndSshGrpList();
1139 userNameList = std::move(userSSHLists.first);
1140 sshGrpUsersList = std::move(userSSHLists.second);
1141
1142 if (!userNameList.empty())
1143 {
1144 std::map<std::string, std::vector<std::string>> groupLists;
Patrick Williams9638afb2021-02-22 17:16:24 -06001145 for (auto& grp : groupsMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301146 {
1147 if (grp == grpSsh)
1148 {
1149 groupLists.emplace(grp, sshGrpUsersList);
1150 }
1151 else
1152 {
1153 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1154 groupLists.emplace(grp, grpUsersList);
1155 }
1156 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001157 for (auto& grp : privMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301158 {
1159 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1160 groupLists.emplace(grp, grpUsersList);
1161 }
1162
Patrick Williams9638afb2021-02-22 17:16:24 -06001163 for (auto& user : userNameList)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301164 {
1165 std::vector<std::string> userGroups;
1166 std::string userPriv;
Patrick Williams9638afb2021-02-22 17:16:24 -06001167 for (const auto& grp : groupLists)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301168 {
1169 std::vector<std::string> tempGrp = grp.second;
1170 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1171 tempGrp.end())
1172 {
1173 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1174 privMgr.end())
1175 {
1176 userPriv = grp.first;
1177 }
1178 else
1179 {
1180 userGroups.emplace_back(grp.first);
1181 }
1182 }
1183 }
1184 // Add user objects to the Users path.
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +05301185 sdbusplus::message::object_path tempObjPath(usersObjPath);
1186 tempObjPath /= user;
1187 std::string objPath(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301188 std::sort(userGroups.begin(), userGroups.end());
Nan Zhou78d85042022-08-29 17:50:22 +00001189 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1190 bus, objPath.c_str(), userGroups,
1191 userPriv, isUserEnabled(user), *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301192 }
1193 }
1194}
1195
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001196UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
Nan Zhoue48085d2022-10-25 00:07:04 +00001197 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1198 pamPasswdConfigFile(defaultPamPasswdConfigFile),
1199 pamAuthConfigFile(defaultPamAuthConfigFile)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301200{
1201 UserMgrIface::allPrivileges(privMgr);
1202 std::sort(groupsMgr.begin(), groupsMgr.end());
1203 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301204 std::string valueStr;
1205 auto value = minPasswdLength;
1206 unsigned long tmp = 0;
1207 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1208 success)
1209 {
1210 AccountPolicyIface::minPasswordLength(minPasswdLength);
1211 }
1212 else
1213 {
1214 try
1215 {
1216 tmp = std::stoul(valueStr, nullptr);
1217 if (tmp > std::numeric_limits<decltype(value)>::max())
1218 {
1219 throw std::out_of_range("Out of range");
1220 }
1221 value = static_cast<decltype(value)>(tmp);
1222 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001223 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301224 {
1225 log<level::ERR>("Exception for MinPasswordLength",
1226 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001227 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301228 }
1229 AccountPolicyIface::minPasswordLength(value);
1230 }
1231 valueStr.clear();
1232 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1233 success)
1234 {
1235 AccountPolicyIface::rememberOldPasswordTimes(0);
1236 }
1237 else
1238 {
1239 value = 0;
1240 try
1241 {
1242 tmp = std::stoul(valueStr, nullptr);
1243 if (tmp > std::numeric_limits<decltype(value)>::max())
1244 {
1245 throw std::out_of_range("Out of range");
1246 }
1247 value = static_cast<decltype(value)>(tmp);
1248 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001249 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301250 {
1251 log<level::ERR>("Exception for RememberOldPasswordTimes",
1252 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001253 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301254 }
1255 AccountPolicyIface::rememberOldPasswordTimes(value);
1256 }
1257 valueStr.clear();
1258 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1259 {
1260 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1261 }
1262 else
1263 {
1264 uint16_t value16 = 0;
1265 try
1266 {
1267 tmp = std::stoul(valueStr, nullptr);
1268 if (tmp > std::numeric_limits<decltype(value16)>::max())
1269 {
1270 throw std::out_of_range("Out of range");
1271 }
1272 value16 = static_cast<decltype(value16)>(tmp);
1273 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001274 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301275 {
1276 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1277 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001278 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301279 }
1280 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1281 }
1282 valueStr.clear();
1283 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1284 {
1285 AccountPolicyIface::accountUnlockTimeout(0);
1286 }
1287 else
1288 {
1289 uint32_t value32 = 0;
1290 try
1291 {
1292 tmp = std::stoul(valueStr, nullptr);
1293 if (tmp > std::numeric_limits<decltype(value32)>::max())
1294 {
1295 throw std::out_of_range("Out of range");
1296 }
1297 value32 = static_cast<decltype(value32)>(tmp);
1298 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001299 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301300 {
1301 log<level::ERR>("Exception for AccountUnlockTimeout",
1302 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001303 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301304 }
1305 AccountPolicyIface::accountUnlockTimeout(value32);
1306 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301307 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301308
1309 // emit the signal
1310 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301311}
1312
Nan Zhou49c81362022-10-25 00:07:08 +00001313void UserMgr::executeUserAdd(const char* userName, const char* groups,
1314 bool sshRequested, bool enabled)
1315{
1316 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1317 // 1970-01-01, that's an implementation-defined behavior
1318 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
1319 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
1320 (enabled ? "" : "1970-01-01"));
1321}
1322
1323void UserMgr::executeUserDelete(const char* userName)
1324{
1325 executeCmd("/usr/sbin/userdel", userName, "-r");
1326}
1327
Nan Zhouf25443e2022-10-25 00:07:11 +00001328void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1329{
1330 std::string newHomeDir = "/home/";
1331 newHomeDir += newUserName;
1332 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1333 newHomeDir.c_str(), "-m");
1334}
1335
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301336} // namespace user
1337} // namespace phosphor