blob: 90631eecce773013197e265d87fa2148b36288a9 [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 int success = 0;
60static constexpr int failure = -1;
61
62// pam modules related
Patrick Williams9638afb2021-02-22 17:16:24 -060063static constexpr const char* pamTally2 = "pam_tally2.so";
64static constexpr const char* pamCrackLib = "pam_cracklib.so";
65static constexpr const char* pamPWHistory = "pam_pwhistory.so";
66static constexpr const char* minPasswdLenProp = "minlen";
67static constexpr const char* remOldPasswdCount = "remember";
68static constexpr const char* maxFailedAttempt = "deny";
69static constexpr const char* unlockTimeout = "unlock_time";
Nan Zhoue48085d2022-10-25 00:07:04 +000070static constexpr const char* defaultPamPasswdConfigFile =
71 "/etc/pam.d/common-password";
72static constexpr const char* defaultPamAuthConfigFile =
73 "/etc/pam.d/common-auth";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053074
Ratan Guptaaeaf9412019-02-11 04:41:52 -060075// Object Manager related
Patrick Williams9638afb2021-02-22 17:16:24 -060076static constexpr const char* ldapMgrObjBasePath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060077 "/xyz/openbmc_project/user/ldap";
78
79// Object Mapper related
Patrick Williams9638afb2021-02-22 17:16:24 -060080static constexpr const char* objMapperService =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060081 "xyz.openbmc_project.ObjectMapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060082static constexpr const char* objMapperPath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060083 "/xyz/openbmc_project/object_mapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060084static constexpr const char* objMapperInterface =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060085 "xyz.openbmc_project.ObjectMapper";
86
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053087using namespace phosphor::logging;
88using InsufficientPermission =
89 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
90using InternalFailure =
91 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
92using InvalidArgument =
93 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
94using UserNameExists =
95 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
96using UserNameDoesNotExist =
97 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
98using UserNameGroupFail =
99 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530100using NoResource =
101 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
102
103using Argument = xyz::openbmc_project::Common::InvalidArgument;
104
Nan Zhoue47c09d2022-10-25 00:06:41 +0000105std::string getCSVFromVector(std::span<const std::string> vec)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530106{
Nan Zhoue47c09d2022-10-25 00:06:41 +0000107 if (vec.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530108 {
Nan Zhoue47c09d2022-10-25 00:06:41 +0000109 return "";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530110 }
Nan Zhoue47c09d2022-10-25 00:06:41 +0000111 return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
112 [](std::string&& val, std::string_view element) {
113 val += ',';
114 val += element;
115 return val;
116 });
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530117}
118
Nan Zhou332fb9d2022-10-25 00:07:03 +0000119bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530120{
121 std::string::size_type delStrPos = csvStr.find(delStr);
122 if (delStrPos != std::string::npos)
123 {
124 // need to also delete the comma char
125 if (delStrPos == 0)
126 {
127 csvStr.erase(delStrPos, delStr.size() + 1);
128 }
129 else
130 {
131 csvStr.erase(delStrPos - 1, delStr.size() + 1);
132 }
133 return true;
134 }
135 return false;
136}
137
Patrick Williams9638afb2021-02-22 17:16:24 -0600138bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530139{
140 if (userName.empty())
141 {
142 log<level::ERR>("User name is empty");
143 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
144 Argument::ARGUMENT_VALUE("Null"));
145 }
146 if (usersList.find(userName) == usersList.end())
147 {
148 return false;
149 }
150 return true;
151}
152
Patrick Williams9638afb2021-02-22 17:16:24 -0600153void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530154{
Nan Zhou8a11d992022-10-25 00:07:06 +0000155 if (!isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530156 {
157 log<level::ERR>("User does not exist",
158 entry("USER_NAME=%s", userName.c_str()));
159 elog<UserNameDoesNotExist>();
160 }
161}
162
Patrick Williams9638afb2021-02-22 17:16:24 -0600163void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530164{
Nan Zhou8a11d992022-10-25 00:07:06 +0000165 if (isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530166 {
167 log<level::ERR>("User already exists",
168 entry("USER_NAME=%s", userName.c_str()));
169 elog<UserNameExists>();
170 }
171}
172
173void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600174 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530175{
176 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
177 groupNames.end())
178 {
179 if (userName.length() > ipmiMaxUserNameLen)
180 {
181 log<level::ERR>("IPMI user name length limitation",
182 entry("SIZE=%d", userName.length()));
183 elog<UserNameGroupFail>(
184 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
185 "IPMI length"));
186 }
187 }
188 if (userName.length() > systemMaxUserNameLen)
189 {
190 log<level::ERR>("User name length limitation",
191 entry("SIZE=%d", userName.length()));
192 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
193 Argument::ARGUMENT_VALUE("Invalid length"));
194 }
195 if (!std::regex_match(userName.c_str(),
196 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
197 {
198 log<level::ERR>("Invalid user name",
199 entry("USER_NAME=%s", userName.c_str()));
200 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
201 Argument::ARGUMENT_VALUE("Invalid data"));
202 }
203}
204
205void UserMgr::throwForMaxGrpUserCount(
Patrick Williams9638afb2021-02-22 17:16:24 -0600206 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530207{
208 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
209 groupNames.end())
210 {
211 if (getIpmiUsersCount() >= ipmiMaxUsers)
212 {
213 log<level::ERR>("IPMI user limit reached");
214 elog<NoResource>(
215 xyz::openbmc_project::User::Common::NoResource::REASON(
216 "ipmi user count reached"));
217 }
218 }
219 else
220 {
221 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
222 (maxSystemUsers - ipmiMaxUsers))
223 {
224 log<level::ERR>("Non-ipmi User limit reached");
225 elog<NoResource>(
226 xyz::openbmc_project::User::Common::NoResource::REASON(
227 "Non-ipmi user count reached"));
228 }
229 }
230 return;
231}
232
Patrick Williams9638afb2021-02-22 17:16:24 -0600233void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530234{
235 if (!priv.empty() &&
236 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
237 {
238 log<level::ERR>("Invalid privilege");
239 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
240 Argument::ARGUMENT_VALUE(priv.c_str()));
241 }
242}
243
Patrick Williams9638afb2021-02-22 17:16:24 -0600244void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530245{
Patrick Williams9638afb2021-02-22 17:16:24 -0600246 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530247 {
248 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
249 groupsMgr.end())
250 {
251 log<level::ERR>("Invalid Group Name listed");
252 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
253 Argument::ARGUMENT_VALUE(group.c_str()));
254 }
255 }
256}
257
258void UserMgr::createUser(std::string userName,
259 std::vector<std::string> groupNames, std::string priv,
260 bool enabled)
261{
262 throwForInvalidPrivilege(priv);
263 throwForInvalidGroups(groupNames);
264 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500265 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530266 throwForUserExists(userName);
267 throwForUserNameConstraints(userName, groupNames);
268 throwForMaxGrpUserCount(groupNames);
269
270 std::string groups = getCSVFromVector(groupNames);
271 bool sshRequested = removeStringFromCSV(groups, grpSsh);
272
273 // treat privilege as a group - This is to avoid using different file to
274 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530275 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530276 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530277 if (groups.size() != 0)
278 {
279 groups += ",";
280 }
281 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530282 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530283 try
284 {
Nan Zhou49c81362022-10-25 00:07:08 +0000285 executeUserAdd(userName.c_str(), groups.c_str(), sshRequested, enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530286 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600287 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530288 {
289 log<level::ERR>("Unable to create new user");
290 elog<InternalFailure>();
291 }
292
293 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530294 sdbusplus::message::object_path tempObjPath(usersObjPath);
295 tempObjPath /= userName;
296 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530297 std::sort(groupNames.begin(), groupNames.end());
298 usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +0000299 userName, std::make_unique<phosphor::user::Users>(
300 bus, userObj.c_str(), groupNames, priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530301
302 log<level::INFO>("User created successfully",
303 entry("USER_NAME=%s", userName.c_str()));
304 return;
305}
306
307void UserMgr::deleteUser(std::string userName)
308{
309 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500310 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530311 throwForUserDoesNotExist(userName);
312 try
313 {
Nan Zhou49c81362022-10-25 00:07:08 +0000314 executeUserDelete(userName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530315 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600316 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530317 {
318 log<level::ERR>("User delete failed",
319 entry("USER_NAME=%s", userName.c_str()));
320 elog<InternalFailure>();
321 }
322
323 usersList.erase(userName);
324
325 log<level::INFO>("User deleted successfully",
326 entry("USER_NAME=%s", userName.c_str()));
327 return;
328}
329
330void UserMgr::renameUser(std::string userName, std::string newUserName)
331{
332 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500333 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530334 throwForUserDoesNotExist(userName);
335 throwForUserExists(newUserName);
336 throwForUserNameConstraints(newUserName,
337 usersList[userName].get()->userGroups());
338 try
339 {
Nan Zhouf25443e2022-10-25 00:07:11 +0000340 executeUserRename(userName.c_str(), newUserName.c_str());
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530341 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600342 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530343 {
344 log<level::ERR>("User rename failed",
345 entry("USER_NAME=%s", userName.c_str()));
346 elog<InternalFailure>();
347 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600348 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530349 std::string priv = user.get()->userPrivilege();
350 std::vector<std::string> groupNames = user.get()->userGroups();
351 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530352 sdbusplus::message::object_path tempObjPath(usersObjPath);
353 tempObjPath /= newUserName;
354 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530355 // Special group 'ipmi' needs a way to identify user renamed, in order to
356 // update encrypted password. It can't rely only on InterfacesRemoved &
357 // InterfacesAdded. So first send out userRenamed signal.
358 this->userRenamed(userName, newUserName);
359 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000360 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
361 bus, newUserObj.c_str(), groupNames,
362 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530363 return;
364}
365
Patrick Williams9638afb2021-02-22 17:16:24 -0600366void UserMgr::updateGroupsAndPriv(const std::string& userName,
Nan Zhoufef63032022-10-25 00:07:12 +0000367 std::vector<std::string> groupNames,
Patrick Williams9638afb2021-02-22 17:16:24 -0600368 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530369{
370 throwForInvalidPrivilege(priv);
371 throwForInvalidGroups(groupNames);
372 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500373 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530374 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600375 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530376 usersList[userName].get()->userGroups();
377 std::vector<std::string> groupDiff;
378 // Note: already dealing with sorted group lists.
379 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
380 groupNames.begin(), groupNames.end(),
381 std::back_inserter(groupDiff));
382 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
383 groupDiff.end())
384 {
385 throwForUserNameConstraints(userName, groupNames);
386 throwForMaxGrpUserCount(groupNames);
387 }
388
389 std::string groups = getCSVFromVector(groupNames);
390 bool sshRequested = removeStringFromCSV(groups, grpSsh);
391
392 // treat privilege as a group - This is to avoid using different file to
393 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530394 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530395 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530396 if (groups.size() != 0)
397 {
398 groups += ",";
399 }
400 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530401 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530402 try
403 {
Nan Zhoufef63032022-10-25 00:07:12 +0000404 executeUserModify(userName.c_str(), groups.c_str(), sshRequested);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530405 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600406 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530407 {
408 log<level::ERR>("Unable to modify user privilege / groups");
409 elog<InternalFailure>();
410 }
411
412 log<level::INFO>("User groups / privilege updated successfully",
413 entry("USER_NAME=%s", userName.c_str()));
Nan Zhoufef63032022-10-25 00:07:12 +0000414 std::sort(groupNames.begin(), groupNames.end());
415 usersList[userName]->setUserGroups(groupNames);
416 usersList[userName]->setUserPrivilege(priv);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530417 return;
418}
419
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530420uint8_t UserMgr::minPasswordLength(uint8_t value)
421{
422 if (value == AccountPolicyIface::minPasswordLength())
423 {
424 return value;
425 }
426 if (value < minPasswdLength)
427 {
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000428 log<level::ERR>(("Attempting to set minPasswordLength to less than " +
429 std::to_string(minPasswdLength))
430 .c_str(),
431 entry("SIZE=%d", value));
432 elog<InvalidArgument>(
433 Argument::ARGUMENT_NAME("minPasswordLength"),
434 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530435 }
436 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
437 std::to_string(value)) != success)
438 {
439 log<level::ERR>("Unable to set minPasswordLength");
440 elog<InternalFailure>();
441 }
442 return AccountPolicyIface::minPasswordLength(value);
443}
444
445uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
446{
447 if (value == AccountPolicyIface::rememberOldPasswordTimes())
448 {
449 return value;
450 }
451 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
452 std::to_string(value)) != success)
453 {
454 log<level::ERR>("Unable to set rememberOldPasswordTimes");
455 elog<InternalFailure>();
456 }
457 return AccountPolicyIface::rememberOldPasswordTimes(value);
458}
459
460uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
461{
462 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
463 {
464 return value;
465 }
466 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
467 std::to_string(value)) != success)
468 {
469 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
470 elog<InternalFailure>();
471 }
472 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
473}
474
475uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
476{
477 if (value == AccountPolicyIface::accountUnlockTimeout())
478 {
479 return value;
480 }
481 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
482 success)
483 {
484 log<level::ERR>("Unable to set accountUnlockTimeout");
485 elog<InternalFailure>();
486 }
487 return AccountPolicyIface::accountUnlockTimeout(value);
488}
489
Patrick Williams9638afb2021-02-22 17:16:24 -0600490int UserMgr::getPamModuleArgValue(const std::string& moduleName,
491 const std::string& argName,
492 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530493{
494 std::string fileName;
495 if (moduleName == pamTally2)
496 {
497 fileName = pamAuthConfigFile;
498 }
499 else
500 {
501 fileName = pamPasswdConfigFile;
502 }
503 std::ifstream fileToRead(fileName, std::ios::in);
504 if (!fileToRead.is_open())
505 {
506 log<level::ERR>("Failed to open pam configuration file",
507 entry("FILE_NAME=%s", fileName.c_str()));
508 return failure;
509 }
510 std::string line;
511 auto argSearch = argName + "=";
512 size_t startPos = 0;
513 size_t endPos = 0;
514 while (getline(fileToRead, line))
515 {
516 // skip comments section starting with #
517 if ((startPos = line.find('#')) != std::string::npos)
518 {
519 if (startPos == 0)
520 {
521 continue;
522 }
523 // skip comments after meaningful section and process those
524 line = line.substr(0, startPos);
525 }
526 if (line.find(moduleName) != std::string::npos)
527 {
528 if ((startPos = line.find(argSearch)) != std::string::npos)
529 {
530 if ((endPos = line.find(' ', startPos)) == std::string::npos)
531 {
532 endPos = line.size();
533 }
534 startPos += argSearch.size();
535 argValue = line.substr(startPos, endPos - startPos);
536 return success;
537 }
538 }
539 }
540 return failure;
541}
542
Patrick Williams9638afb2021-02-22 17:16:24 -0600543int UserMgr::setPamModuleArgValue(const std::string& moduleName,
544 const std::string& argName,
545 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530546{
547 std::string fileName;
548 if (moduleName == pamTally2)
549 {
550 fileName = pamAuthConfigFile;
551 }
552 else
553 {
554 fileName = pamPasswdConfigFile;
555 }
556 std::string tmpFileName = fileName + "_tmp";
557 std::ifstream fileToRead(fileName, std::ios::in);
558 std::ofstream fileToWrite(tmpFileName, std::ios::out);
559 if (!fileToRead.is_open() || !fileToWrite.is_open())
560 {
561 log<level::ERR>("Failed to open pam configuration /tmp file",
562 entry("FILE_NAME=%s", fileName.c_str()));
563 return failure;
564 }
565 std::string line;
566 auto argSearch = argName + "=";
567 size_t startPos = 0;
568 size_t endPos = 0;
569 bool found = false;
570 while (getline(fileToRead, line))
571 {
572 // skip comments section starting with #
573 if ((startPos = line.find('#')) != std::string::npos)
574 {
575 if (startPos == 0)
576 {
577 fileToWrite << line << std::endl;
578 continue;
579 }
580 // skip comments after meaningful section and process those
581 line = line.substr(0, startPos);
582 }
583 if (line.find(moduleName) != std::string::npos)
584 {
585 if ((startPos = line.find(argSearch)) != std::string::npos)
586 {
587 if ((endPos = line.find(' ', startPos)) == std::string::npos)
588 {
589 endPos = line.size();
590 }
591 startPos += argSearch.size();
592 fileToWrite << line.substr(0, startPos) << argValue
593 << line.substr(endPos, line.size() - endPos)
594 << std::endl;
595 found = true;
596 continue;
597 }
598 }
599 fileToWrite << line << std::endl;
600 }
601 fileToWrite.close();
602 fileToRead.close();
603 if (found)
604 {
605 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
606 {
607 return success;
608 }
609 }
610 return failure;
611}
612
Patrick Williams9638afb2021-02-22 17:16:24 -0600613void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530614{
615 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500616 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530617 throwForUserDoesNotExist(userName);
618 try
619 {
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000620 executeUserModifyUserEnable(userName.c_str(), enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530621 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600622 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530623 {
624 log<level::ERR>("Unable to modify user enabled state");
625 elog<InternalFailure>();
626 }
627
628 log<level::INFO>("User enabled/disabled state updated successfully",
629 entry("USER_NAME=%s", userName.c_str()),
630 entry("ENABLED=%d", enabled));
Nan Zhou6b6f2d82022-10-25 00:07:17 +0000631 usersList[userName]->setUserEnabled(enabled);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530632 return;
633}
634
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530635/**
636 * pam_tally2 app will provide the user failure count and failure status
637 * in second line of output with words position [0] - user name,
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800638 * [1] - failure count, [2] - latest failure date, [3] - latest failure time
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530639 * [4] - failure app
640 **/
641
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530642static constexpr size_t t2FailCntIdx = 1;
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800643static constexpr size_t t2FailDateIdx = 2;
644static constexpr size_t t2FailTimeIdx = 3;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530645static constexpr size_t t2OutputIndex = 1;
646
Patrick Williams9638afb2021-02-22 17:16:24 -0600647bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530648{
649 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500650 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800651 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
652 {
653 return false;
654 }
655
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530656 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800657 try
658 {
Nan Zhoua2953032022-11-11 21:50:32 +0000659 output = getFailedAttempt(userName.c_str());
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800660 }
661 catch (const InternalFailure& e)
662 {
663 log<level::ERR>("Unable to read login failure counter");
664 elog<InternalFailure>();
665 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530666
667 std::vector<std::string> splitWords;
668 boost::algorithm::split(splitWords, output[t2OutputIndex],
669 boost::algorithm::is_any_of("\t "),
670 boost::token_compress_on);
671
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800672 uint16_t failAttempts = 0;
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530673 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530674 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530675 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800676 if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530677 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530678 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530679 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800680 failAttempts = static_cast<decltype(failAttempts)>(tmp);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530681 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600682 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530683 {
684 log<level::ERR>("Exception for userLockedForFailedAttempt",
685 entry("WHAT=%s", e.what()));
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800686 elog<InternalFailure>();
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530687 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800688
689 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
690 {
691 return false;
692 }
693
694 // When failedAttempts is not 0, Latest failure date/time should be
695 // available
696 if (splitWords.size() < 4)
697 {
698 log<level::ERR>("Unable to read latest failure date/time");
699 elog<InternalFailure>();
700 }
701
702 const std::string failDateTime =
703 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
704
705 // NOTE: Cannot use std::get_time() here as the implementation of %y in
706 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
707 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
708 std::tm tmStruct = {};
709 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
710 {
711 log<level::ERR>("Failed to parse latest failure date/time");
712 elog<InternalFailure>();
713 }
714
715 time_t failTimestamp = std::mktime(&tmStruct);
716 if (failTimestamp +
717 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
718 std::time(NULL))
719 {
720 return false;
721 }
722
723 return true;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530724}
725
Patrick Williams9638afb2021-02-22 17:16:24 -0600726bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
727 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530728{
729 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500730 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530731 if (value == true)
732 {
733 return userLockedForFailedAttempt(userName);
734 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530735
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800736 try
737 {
738 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
739 }
740 catch (const InternalFailure& e)
741 {
742 log<level::ERR>("Unable to reset login failure counter");
743 elog<InternalFailure>();
744 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530745
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530746 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530747}
748
Patrick Williams9638afb2021-02-22 17:16:24 -0600749bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600750{
751 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500752 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600753
754 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600755 {};
756 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600757 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
758 if (buflen < -1)
759 {
760 // Use a default size if there is no hard limit suggested by sysconf()
761 buflen = 1024;
762 }
763 std::vector<char> buffer(buflen);
764 auto status =
765 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
766 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
767 // If no matching password record was found, these functions return 0
768 // and store NULL in *spwdPtr
769 if ((status == 0) && (&spwd == spwdPtr))
770 {
771 // Determine password validity per "chage" docs, where:
772 // spwd.sp_lstchg == 0 means password is expired, and
773 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000774 constexpr long secondsPerDay = 60 * 60 * 24;
775 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600776 if ((spwd.sp_lstchg == 0) ||
777 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
778 {
779 return true;
780 }
781 }
782 else
783 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000784 // User entry is missing in /etc/shadow, indicating no SHA password.
785 // Treat this as new user without password entry in /etc/shadow
786 // TODO: Add property to indicate user password was not set yet
787 // https://github.com/openbmc/phosphor-user-manager/issues/8
788 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600789 }
790
791 return false;
792}
793
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530794UserSSHLists UserMgr::getUserAndSshGrpList()
795{
796 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500797 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530798
799 std::vector<std::string> userList;
800 std::vector<std::string> sshUsersList;
801 struct passwd pw, *pwp = nullptr;
802 std::array<char, 1024> buffer{};
803
804 phosphor::user::File passwd(passwdFileName, "r");
805 if ((passwd)() == NULL)
806 {
807 log<level::ERR>("Error opening the passwd file");
808 elog<InternalFailure>();
809 }
810
811 while (true)
812 {
813 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
814 &pwp);
815 if ((r != 0) || (pwp == NULL))
816 {
817 // Any error, break the loop.
818 break;
819 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530820#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530821 // Add all users whose UID >= 1000 and < 65534
822 // and special UID 0.
823 if ((pwp->pw_uid == 0) ||
824 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530825#else
826 // Add all users whose UID >=1000 and < 65534
827 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
828#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530829 {
830 std::string userName(pwp->pw_name);
831 userList.emplace_back(userName);
832
833 // ssh doesn't have separate group. Check login shell entry to
834 // get all users list which are member of ssh group.
835 std::string loginShell(pwp->pw_shell);
836 if (loginShell == "/bin/sh")
837 {
838 sshUsersList.emplace_back(userName);
839 }
840 }
841 }
842 endpwent();
843 return std::make_pair(std::move(userList), std::move(sshUsersList));
844}
845
846size_t UserMgr::getIpmiUsersCount()
847{
848 std::vector<std::string> userList = getUsersInGroup("ipmi");
849 return userList.size();
850}
851
Nan Zhou49c81362022-10-25 00:07:08 +0000852size_t UserMgr::getNonIpmiUsersCount()
853{
854 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
855 return usersList.size() - ipmiUsers.size();
856}
857
Patrick Williams9638afb2021-02-22 17:16:24 -0600858bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530859{
860 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500861 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530862 std::array<char, 4096> buffer{};
863 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600864 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530865 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
866 buffer.max_size(), &resultPtr);
867 if (!status && (&spwd == resultPtr))
868 {
869 if (resultPtr->sp_expire >= 0)
870 {
871 return false; // user locked out
872 }
873 return true;
874 }
875 return false; // assume user is disabled for any error.
876}
877
Patrick Williams9638afb2021-02-22 17:16:24 -0600878std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530879{
880 std::vector<std::string> usersInGroup;
881 // Should be more than enough to get the pwd structure.
882 std::array<char, 4096> buffer{};
883 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600884 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530885
886 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
887 buffer.max_size(), &resultPtr);
888
889 if (!status && (&grp == resultPtr))
890 {
891 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
892 {
893 usersInGroup.emplace_back(*(grp.gr_mem));
894 }
895 }
896 else
897 {
898 log<level::ERR>("Group not found",
899 entry("GROUP=%s", groupName.c_str()));
900 // Don't throw error, just return empty userList - fallback
901 }
902 return usersInGroup;
903}
904
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600905DbusUserObj UserMgr::getPrivilegeMapperObject(void)
906{
907 DbusUserObj objects;
908 try
909 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500910 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
911 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600912
913 auto ldapMgmtService =
914 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600915 auto method = bus.new_method_call(
916 ldapMgmtService.c_str(), ldapMgrObjBasePath,
917 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
918
919 auto reply = bus.call(method);
920 reply.read(objects);
921 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600922 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600923 {
924 log<level::ERR>("Unable to get the User Service",
925 entry("WHAT=%s", e.what()));
926 throw;
927 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -0500928 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600929 {
930 log<level::ERR>(
931 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
932 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
933 throw;
934 }
935 return objects;
936}
937
Patrick Williams9638afb2021-02-22 17:16:24 -0600938std::string UserMgr::getLdapGroupName(const std::string& userName)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600939{
940 struct passwd pwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600941 {};
942 struct passwd* pwdPtr = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600943 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
944 if (buflen < -1)
945 {
946 // Use a default size if there is no hard limit suggested by sysconf()
947 buflen = 1024;
948 }
949 std::vector<char> buffer(buflen);
950 gid_t gid = 0;
951
952 auto status =
953 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
954 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
955 // If no matching password record was found, these functions return 0
956 // and store NULL in *pwdPtr
957 if (!status && (&pwd == pwdPtr))
958 {
959 gid = pwd.pw_gid;
960 }
961 else
962 {
963 log<level::ERR>("User does not exist",
964 entry("USER_NAME=%s", userName.c_str()));
965 elog<UserNameDoesNotExist>();
966 }
967
Patrick Williams9638afb2021-02-22 17:16:24 -0600968 struct group* groups = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600969 std::string ldapGroupName;
970
971 while ((groups = getgrent()) != NULL)
972 {
973 if (groups->gr_gid == gid)
974 {
975 ldapGroupName = groups->gr_name;
976 break;
977 }
978 }
979 // Call endgrent() to close the group database.
980 endgrent();
981
982 return ldapGroupName;
983}
984
Patrick Williams9638afb2021-02-22 17:16:24 -0600985std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600986{
987 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
988 objMapperInterface, "GetObject");
989
990 mapperCall.append(std::move(path));
991 mapperCall.append(std::vector<std::string>({std::move(intf)}));
992
993 auto mapperResponseMsg = bus.call(mapperCall);
994
995 if (mapperResponseMsg.is_method_error())
996 {
997 log<level::ERR>("Error in mapper call");
998 elog<InternalFailure>();
999 }
1000
1001 std::map<std::string, std::vector<std::string>> mapperResponse;
1002 mapperResponseMsg.read(mapperResponse);
1003
1004 if (mapperResponse.begin() == mapperResponse.end())
1005 {
1006 log<level::ERR>("Invalid response from mapper");
1007 elog<InternalFailure>();
1008 }
1009
1010 return mapperResponse.begin()->first;
1011}
1012
1013UserInfoMap UserMgr::getUserInfo(std::string userName)
1014{
1015 UserInfoMap userInfo;
1016 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001017 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001018 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001019 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001020 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1021 userInfo.emplace("UserGroups", user.get()->userGroups());
1022 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1023 userInfo.emplace("UserLockedForFailedAttempt",
1024 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001025 userInfo.emplace("UserPasswordExpired",
1026 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001027 userInfo.emplace("RemoteUser", false);
1028 }
1029 else
1030 {
1031 std::string ldapGroupName = getLdapGroupName(userName);
1032 if (ldapGroupName.empty())
1033 {
1034 log<level::ERR>("Unable to get group name",
1035 entry("USER_NAME=%s", userName.c_str()));
1036 elog<InternalFailure>();
1037 }
1038
1039 DbusUserObj objects = getPrivilegeMapperObject();
1040
Ravi Teja5fe724a2019-05-07 05:14:42 -05001041 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001042 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001043
1044 try
1045 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001046 for (const auto& obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001047 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001048 for (const auto& interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001049 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001050 if ((interface.first ==
1051 "xyz.openbmc_project.Object.Enable"))
1052 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001053 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001054 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001055 auto value = std::get<bool>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001056 if ((property.first == "Enabled") &&
1057 (value == true))
1058 {
1059 ldapConfigPath = obj.first;
1060 break;
1061 }
1062 }
1063 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001064 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001065 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001066 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001067 break;
1068 }
1069 }
1070
1071 if (ldapConfigPath.empty())
1072 {
1073 return userInfo;
1074 }
1075
Patrick Williams9638afb2021-02-22 17:16:24 -06001076 for (const auto& obj : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001077 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001078 for (const auto& interface : obj.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001079 {
1080 if ((interface.first ==
1081 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1082 (obj.first.str.find(ldapConfigPath) !=
1083 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001084 {
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001085 std::string privilege;
1086 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001087
Patrick Williams9638afb2021-02-22 17:16:24 -06001088 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001089 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001090 auto value = std::get<std::string>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001091 if (property.first == "GroupName")
1092 {
1093 groupName = value;
1094 }
1095 else if (property.first == "Privilege")
1096 {
1097 privilege = value;
1098 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001099 }
1100 if (groupName == ldapGroupName)
1101 {
1102 userPrivilege = privilege;
1103 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001104 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001105 }
1106 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001107 if (!userPrivilege.empty())
1108 {
1109 break;
1110 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001111 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001112
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001113 if (userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001114 {
1115 log<level::ERR>("LDAP group privilege mapping does not exist");
1116 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001117 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001118 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001119 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001120 {
1121 log<level::ERR>("Error while accessing variant",
1122 entry("WHAT=%s", e.what()));
1123 elog<InternalFailure>();
1124 }
1125 userInfo.emplace("RemoteUser", true);
1126 }
1127
1128 return userInfo;
1129}
1130
Nan Zhou4bc69812022-10-25 00:07:13 +00001131void UserMgr::initializeAccountPolicy()
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301132{
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301133 std::string valueStr;
1134 auto value = minPasswdLength;
1135 unsigned long tmp = 0;
1136 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1137 success)
1138 {
1139 AccountPolicyIface::minPasswordLength(minPasswdLength);
1140 }
1141 else
1142 {
1143 try
1144 {
1145 tmp = std::stoul(valueStr, nullptr);
1146 if (tmp > std::numeric_limits<decltype(value)>::max())
1147 {
1148 throw std::out_of_range("Out of range");
1149 }
1150 value = static_cast<decltype(value)>(tmp);
1151 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001152 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301153 {
1154 log<level::ERR>("Exception for MinPasswordLength",
1155 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001156 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301157 }
1158 AccountPolicyIface::minPasswordLength(value);
1159 }
1160 valueStr.clear();
1161 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1162 success)
1163 {
1164 AccountPolicyIface::rememberOldPasswordTimes(0);
1165 }
1166 else
1167 {
1168 value = 0;
1169 try
1170 {
1171 tmp = std::stoul(valueStr, nullptr);
1172 if (tmp > std::numeric_limits<decltype(value)>::max())
1173 {
1174 throw std::out_of_range("Out of range");
1175 }
1176 value = static_cast<decltype(value)>(tmp);
1177 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001178 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301179 {
1180 log<level::ERR>("Exception for RememberOldPasswordTimes",
1181 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001182 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301183 }
1184 AccountPolicyIface::rememberOldPasswordTimes(value);
1185 }
1186 valueStr.clear();
1187 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1188 {
1189 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1190 }
1191 else
1192 {
1193 uint16_t value16 = 0;
1194 try
1195 {
1196 tmp = std::stoul(valueStr, nullptr);
1197 if (tmp > std::numeric_limits<decltype(value16)>::max())
1198 {
1199 throw std::out_of_range("Out of range");
1200 }
1201 value16 = static_cast<decltype(value16)>(tmp);
1202 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001203 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301204 {
1205 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1206 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001207 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301208 }
1209 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1210 }
1211 valueStr.clear();
1212 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1213 {
1214 AccountPolicyIface::accountUnlockTimeout(0);
1215 }
1216 else
1217 {
1218 uint32_t value32 = 0;
1219 try
1220 {
1221 tmp = std::stoul(valueStr, nullptr);
1222 if (tmp > std::numeric_limits<decltype(value32)>::max())
1223 {
1224 throw std::out_of_range("Out of range");
1225 }
1226 value32 = static_cast<decltype(value32)>(tmp);
1227 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001228 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301229 {
1230 log<level::ERR>("Exception for AccountUnlockTimeout",
1231 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001232 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301233 }
1234 AccountPolicyIface::accountUnlockTimeout(value32);
1235 }
Nan Zhou4bc69812022-10-25 00:07:13 +00001236}
1237
1238void UserMgr::initUserObjects(void)
1239{
1240 // All user management lock has to be based on /etc/shadow
1241 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1242 std::vector<std::string> userNameList;
1243 std::vector<std::string> sshGrpUsersList;
1244 UserSSHLists userSSHLists = getUserAndSshGrpList();
1245 userNameList = std::move(userSSHLists.first);
1246 sshGrpUsersList = std::move(userSSHLists.second);
1247
1248 if (!userNameList.empty())
1249 {
1250 std::map<std::string, std::vector<std::string>> groupLists;
1251 for (auto& grp : groupsMgr)
1252 {
1253 if (grp == grpSsh)
1254 {
1255 groupLists.emplace(grp, sshGrpUsersList);
1256 }
1257 else
1258 {
1259 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1260 groupLists.emplace(grp, grpUsersList);
1261 }
1262 }
1263 for (auto& grp : privMgr)
1264 {
1265 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1266 groupLists.emplace(grp, grpUsersList);
1267 }
1268
1269 for (auto& user : userNameList)
1270 {
1271 std::vector<std::string> userGroups;
1272 std::string userPriv;
1273 for (const auto& grp : groupLists)
1274 {
1275 std::vector<std::string> tempGrp = grp.second;
1276 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1277 tempGrp.end())
1278 {
1279 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1280 privMgr.end())
1281 {
1282 userPriv = grp.first;
1283 }
1284 else
1285 {
1286 userGroups.emplace_back(grp.first);
1287 }
1288 }
1289 }
1290 // Add user objects to the Users path.
1291 sdbusplus::message::object_path tempObjPath(usersObjPath);
1292 tempObjPath /= user;
1293 std::string objPath(tempObjPath);
1294 std::sort(userGroups.begin(), userGroups.end());
1295 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1296 bus, objPath.c_str(), userGroups,
1297 userPriv, isUserEnabled(user), *this));
1298 }
1299 }
1300}
1301
1302UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1303 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1304 pamPasswdConfigFile(defaultPamPasswdConfigFile),
1305 pamAuthConfigFile(defaultPamAuthConfigFile)
1306{
1307 UserMgrIface::allPrivileges(privMgr);
1308 std::sort(groupsMgr.begin(), groupsMgr.end());
1309 UserMgrIface::allGroups(groupsMgr);
1310 initializeAccountPolicy();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301311 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301312
1313 // emit the signal
1314 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301315}
1316
Nan Zhou49c81362022-10-25 00:07:08 +00001317void UserMgr::executeUserAdd(const char* userName, const char* groups,
1318 bool sshRequested, bool enabled)
1319{
1320 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1321 // 1970-01-01, that's an implementation-defined behavior
1322 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
1323 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
1324 (enabled ? "" : "1970-01-01"));
1325}
1326
1327void UserMgr::executeUserDelete(const char* userName)
1328{
1329 executeCmd("/usr/sbin/userdel", userName, "-r");
1330}
1331
Nan Zhouf25443e2022-10-25 00:07:11 +00001332void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1333{
1334 std::string newHomeDir = "/home/";
1335 newHomeDir += newUserName;
1336 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1337 newHomeDir.c_str(), "-m");
1338}
1339
Nan Zhoufef63032022-10-25 00:07:12 +00001340void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1341 bool sshRequested)
1342{
1343 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
1344 (sshRequested ? "/bin/sh" : "/bin/nologin"));
1345}
1346
Nan Zhou6b6f2d82022-10-25 00:07:17 +00001347void UserMgr::executeUserModifyUserEnable(const char* userName, bool enabled)
1348{
1349 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1350 // 1970-01-01, that's an implementation-defined behavior
1351 executeCmd("/usr/sbin/usermod", userName, "-e",
1352 (enabled ? "" : "1970-01-01"));
1353}
1354
Nan Zhoua2953032022-11-11 21:50:32 +00001355std::vector<std::string> UserMgr::getFailedAttempt(const char* userName)
1356{
1357 return executeCmd("/usr/sbin/pam_tally2", "-u", userName);
1358}
1359
Nan Zhou86040c22022-11-17 02:08:24 +00001360void UserMgr::createGroup(std::string /*groupName*/)
1361{
1362 log<level::ERR>("Not implemented yet");
1363 elog<InternalFailure>();
1364}
1365
1366void UserMgr::deleteGroup(std::string /*groupName*/)
1367{
1368 log<level::ERR>("Not implemented yet");
1369 elog<InternalFailure>();
1370}
1371
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301372} // namespace user
1373} // namespace phosphor