blob: 7b2b697cf4488c315e04cb9ceb80045ba0636500 [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 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800620 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
621 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530622 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800623 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530624 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600625 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530626 {
627 log<level::ERR>("Unable to modify user enabled state");
628 elog<InternalFailure>();
629 }
630
631 log<level::INFO>("User enabled/disabled state updated successfully",
632 entry("USER_NAME=%s", userName.c_str()),
633 entry("ENABLED=%d", enabled));
634 return;
635}
636
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530637/**
638 * pam_tally2 app will provide the user failure count and failure status
639 * in second line of output with words position [0] - user name,
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800640 * [1] - failure count, [2] - latest failure date, [3] - latest failure time
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530641 * [4] - failure app
642 **/
643
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530644static constexpr size_t t2FailCntIdx = 1;
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800645static constexpr size_t t2FailDateIdx = 2;
646static constexpr size_t t2FailTimeIdx = 3;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530647static constexpr size_t t2OutputIndex = 1;
648
Patrick Williams9638afb2021-02-22 17:16:24 -0600649bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530650{
651 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500652 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800653 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
654 {
655 return false;
656 }
657
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530658 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800659 try
660 {
661 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
662 }
663 catch (const InternalFailure& e)
664 {
665 log<level::ERR>("Unable to read login failure counter");
666 elog<InternalFailure>();
667 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530668
669 std::vector<std::string> splitWords;
670 boost::algorithm::split(splitWords, output[t2OutputIndex],
671 boost::algorithm::is_any_of("\t "),
672 boost::token_compress_on);
673
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800674 uint16_t failAttempts = 0;
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530675 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530676 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530677 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800678 if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530679 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530680 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530681 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800682 failAttempts = static_cast<decltype(failAttempts)>(tmp);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530683 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600684 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530685 {
686 log<level::ERR>("Exception for userLockedForFailedAttempt",
687 entry("WHAT=%s", e.what()));
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800688 elog<InternalFailure>();
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530689 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800690
691 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
692 {
693 return false;
694 }
695
696 // When failedAttempts is not 0, Latest failure date/time should be
697 // available
698 if (splitWords.size() < 4)
699 {
700 log<level::ERR>("Unable to read latest failure date/time");
701 elog<InternalFailure>();
702 }
703
704 const std::string failDateTime =
705 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
706
707 // NOTE: Cannot use std::get_time() here as the implementation of %y in
708 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
709 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
710 std::tm tmStruct = {};
711 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
712 {
713 log<level::ERR>("Failed to parse latest failure date/time");
714 elog<InternalFailure>();
715 }
716
717 time_t failTimestamp = std::mktime(&tmStruct);
718 if (failTimestamp +
719 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
720 std::time(NULL))
721 {
722 return false;
723 }
724
725 return true;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530726}
727
Patrick Williams9638afb2021-02-22 17:16:24 -0600728bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
729 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530730{
731 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500732 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530733 if (value == true)
734 {
735 return userLockedForFailedAttempt(userName);
736 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530737
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800738 try
739 {
740 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
741 }
742 catch (const InternalFailure& e)
743 {
744 log<level::ERR>("Unable to reset login failure counter");
745 elog<InternalFailure>();
746 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530747
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530748 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530749}
750
Patrick Williams9638afb2021-02-22 17:16:24 -0600751bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600752{
753 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500754 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600755
756 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600757 {};
758 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600759 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
760 if (buflen < -1)
761 {
762 // Use a default size if there is no hard limit suggested by sysconf()
763 buflen = 1024;
764 }
765 std::vector<char> buffer(buflen);
766 auto status =
767 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
768 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
769 // If no matching password record was found, these functions return 0
770 // and store NULL in *spwdPtr
771 if ((status == 0) && (&spwd == spwdPtr))
772 {
773 // Determine password validity per "chage" docs, where:
774 // spwd.sp_lstchg == 0 means password is expired, and
775 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000776 constexpr long secondsPerDay = 60 * 60 * 24;
777 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600778 if ((spwd.sp_lstchg == 0) ||
779 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
780 {
781 return true;
782 }
783 }
784 else
785 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000786 // User entry is missing in /etc/shadow, indicating no SHA password.
787 // Treat this as new user without password entry in /etc/shadow
788 // TODO: Add property to indicate user password was not set yet
789 // https://github.com/openbmc/phosphor-user-manager/issues/8
790 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600791 }
792
793 return false;
794}
795
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530796UserSSHLists UserMgr::getUserAndSshGrpList()
797{
798 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500799 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530800
801 std::vector<std::string> userList;
802 std::vector<std::string> sshUsersList;
803 struct passwd pw, *pwp = nullptr;
804 std::array<char, 1024> buffer{};
805
806 phosphor::user::File passwd(passwdFileName, "r");
807 if ((passwd)() == NULL)
808 {
809 log<level::ERR>("Error opening the passwd file");
810 elog<InternalFailure>();
811 }
812
813 while (true)
814 {
815 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
816 &pwp);
817 if ((r != 0) || (pwp == NULL))
818 {
819 // Any error, break the loop.
820 break;
821 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530822#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530823 // Add all users whose UID >= 1000 and < 65534
824 // and special UID 0.
825 if ((pwp->pw_uid == 0) ||
826 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530827#else
828 // Add all users whose UID >=1000 and < 65534
829 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
830#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530831 {
832 std::string userName(pwp->pw_name);
833 userList.emplace_back(userName);
834
835 // ssh doesn't have separate group. Check login shell entry to
836 // get all users list which are member of ssh group.
837 std::string loginShell(pwp->pw_shell);
838 if (loginShell == "/bin/sh")
839 {
840 sshUsersList.emplace_back(userName);
841 }
842 }
843 }
844 endpwent();
845 return std::make_pair(std::move(userList), std::move(sshUsersList));
846}
847
848size_t UserMgr::getIpmiUsersCount()
849{
850 std::vector<std::string> userList = getUsersInGroup("ipmi");
851 return userList.size();
852}
853
Nan Zhou49c81362022-10-25 00:07:08 +0000854size_t UserMgr::getNonIpmiUsersCount()
855{
856 std::vector<std::string> ipmiUsers = getUsersInGroup("ipmi");
857 return usersList.size() - ipmiUsers.size();
858}
859
Patrick Williams9638afb2021-02-22 17:16:24 -0600860bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530861{
862 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500863 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530864 std::array<char, 4096> buffer{};
865 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600866 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530867 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
868 buffer.max_size(), &resultPtr);
869 if (!status && (&spwd == resultPtr))
870 {
871 if (resultPtr->sp_expire >= 0)
872 {
873 return false; // user locked out
874 }
875 return true;
876 }
877 return false; // assume user is disabled for any error.
878}
879
Patrick Williams9638afb2021-02-22 17:16:24 -0600880std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530881{
882 std::vector<std::string> usersInGroup;
883 // Should be more than enough to get the pwd structure.
884 std::array<char, 4096> buffer{};
885 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600886 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530887
888 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
889 buffer.max_size(), &resultPtr);
890
891 if (!status && (&grp == resultPtr))
892 {
893 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
894 {
895 usersInGroup.emplace_back(*(grp.gr_mem));
896 }
897 }
898 else
899 {
900 log<level::ERR>("Group not found",
901 entry("GROUP=%s", groupName.c_str()));
902 // Don't throw error, just return empty userList - fallback
903 }
904 return usersInGroup;
905}
906
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600907DbusUserObj UserMgr::getPrivilegeMapperObject(void)
908{
909 DbusUserObj objects;
910 try
911 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500912 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
913 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600914
915 auto ldapMgmtService =
916 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600917 auto method = bus.new_method_call(
918 ldapMgmtService.c_str(), ldapMgrObjBasePath,
919 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
920
921 auto reply = bus.call(method);
922 reply.read(objects);
923 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600924 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600925 {
926 log<level::ERR>("Unable to get the User Service",
927 entry("WHAT=%s", e.what()));
928 throw;
929 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -0500930 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600931 {
932 log<level::ERR>(
933 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
934 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
935 throw;
936 }
937 return objects;
938}
939
Patrick Williams9638afb2021-02-22 17:16:24 -0600940std::string UserMgr::getLdapGroupName(const std::string& userName)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600941{
942 struct passwd pwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600943 {};
944 struct passwd* pwdPtr = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600945 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
946 if (buflen < -1)
947 {
948 // Use a default size if there is no hard limit suggested by sysconf()
949 buflen = 1024;
950 }
951 std::vector<char> buffer(buflen);
952 gid_t gid = 0;
953
954 auto status =
955 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
956 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
957 // If no matching password record was found, these functions return 0
958 // and store NULL in *pwdPtr
959 if (!status && (&pwd == pwdPtr))
960 {
961 gid = pwd.pw_gid;
962 }
963 else
964 {
965 log<level::ERR>("User does not exist",
966 entry("USER_NAME=%s", userName.c_str()));
967 elog<UserNameDoesNotExist>();
968 }
969
Patrick Williams9638afb2021-02-22 17:16:24 -0600970 struct group* groups = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600971 std::string ldapGroupName;
972
973 while ((groups = getgrent()) != NULL)
974 {
975 if (groups->gr_gid == gid)
976 {
977 ldapGroupName = groups->gr_name;
978 break;
979 }
980 }
981 // Call endgrent() to close the group database.
982 endgrent();
983
984 return ldapGroupName;
985}
986
Patrick Williams9638afb2021-02-22 17:16:24 -0600987std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600988{
989 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
990 objMapperInterface, "GetObject");
991
992 mapperCall.append(std::move(path));
993 mapperCall.append(std::vector<std::string>({std::move(intf)}));
994
995 auto mapperResponseMsg = bus.call(mapperCall);
996
997 if (mapperResponseMsg.is_method_error())
998 {
999 log<level::ERR>("Error in mapper call");
1000 elog<InternalFailure>();
1001 }
1002
1003 std::map<std::string, std::vector<std::string>> mapperResponse;
1004 mapperResponseMsg.read(mapperResponse);
1005
1006 if (mapperResponse.begin() == mapperResponse.end())
1007 {
1008 log<level::ERR>("Invalid response from mapper");
1009 elog<InternalFailure>();
1010 }
1011
1012 return mapperResponse.begin()->first;
1013}
1014
1015UserInfoMap UserMgr::getUserInfo(std::string userName)
1016{
1017 UserInfoMap userInfo;
1018 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001019 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001020 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001021 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001022 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1023 userInfo.emplace("UserGroups", user.get()->userGroups());
1024 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1025 userInfo.emplace("UserLockedForFailedAttempt",
1026 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001027 userInfo.emplace("UserPasswordExpired",
1028 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001029 userInfo.emplace("RemoteUser", false);
1030 }
1031 else
1032 {
1033 std::string ldapGroupName = getLdapGroupName(userName);
1034 if (ldapGroupName.empty())
1035 {
1036 log<level::ERR>("Unable to get group name",
1037 entry("USER_NAME=%s", userName.c_str()));
1038 elog<InternalFailure>();
1039 }
1040
1041 DbusUserObj objects = getPrivilegeMapperObject();
1042
Ravi Teja5fe724a2019-05-07 05:14:42 -05001043 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001044 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001045
1046 try
1047 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001048 for (const auto& obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001049 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001050 for (const auto& interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001051 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001052 if ((interface.first ==
1053 "xyz.openbmc_project.Object.Enable"))
1054 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001055 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001056 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001057 auto value = std::get<bool>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001058 if ((property.first == "Enabled") &&
1059 (value == true))
1060 {
1061 ldapConfigPath = obj.first;
1062 break;
1063 }
1064 }
1065 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001066 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001067 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001068 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001069 break;
1070 }
1071 }
1072
1073 if (ldapConfigPath.empty())
1074 {
1075 return userInfo;
1076 }
1077
Patrick Williams9638afb2021-02-22 17:16:24 -06001078 for (const auto& obj : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001079 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001080 for (const auto& interface : obj.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001081 {
1082 if ((interface.first ==
1083 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1084 (obj.first.str.find(ldapConfigPath) !=
1085 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001086 {
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001087 std::string privilege;
1088 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001089
Patrick Williams9638afb2021-02-22 17:16:24 -06001090 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001091 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001092 auto value = std::get<std::string>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001093 if (property.first == "GroupName")
1094 {
1095 groupName = value;
1096 }
1097 else if (property.first == "Privilege")
1098 {
1099 privilege = value;
1100 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001101 }
1102 if (groupName == ldapGroupName)
1103 {
1104 userPrivilege = privilege;
1105 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001106 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001107 }
1108 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001109 if (!userPrivilege.empty())
1110 {
1111 break;
1112 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001113 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001114
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001115 if (userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001116 {
1117 log<level::ERR>("LDAP group privilege mapping does not exist");
1118 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001119 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001120 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001121 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001122 {
1123 log<level::ERR>("Error while accessing variant",
1124 entry("WHAT=%s", e.what()));
1125 elog<InternalFailure>();
1126 }
1127 userInfo.emplace("RemoteUser", true);
1128 }
1129
1130 return userInfo;
1131}
1132
Nan Zhou4bc69812022-10-25 00:07:13 +00001133void UserMgr::initializeAccountPolicy()
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301134{
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301135 std::string valueStr;
1136 auto value = minPasswdLength;
1137 unsigned long tmp = 0;
1138 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1139 success)
1140 {
1141 AccountPolicyIface::minPasswordLength(minPasswdLength);
1142 }
1143 else
1144 {
1145 try
1146 {
1147 tmp = std::stoul(valueStr, nullptr);
1148 if (tmp > std::numeric_limits<decltype(value)>::max())
1149 {
1150 throw std::out_of_range("Out of range");
1151 }
1152 value = static_cast<decltype(value)>(tmp);
1153 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001154 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301155 {
1156 log<level::ERR>("Exception for MinPasswordLength",
1157 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001158 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301159 }
1160 AccountPolicyIface::minPasswordLength(value);
1161 }
1162 valueStr.clear();
1163 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1164 success)
1165 {
1166 AccountPolicyIface::rememberOldPasswordTimes(0);
1167 }
1168 else
1169 {
1170 value = 0;
1171 try
1172 {
1173 tmp = std::stoul(valueStr, nullptr);
1174 if (tmp > std::numeric_limits<decltype(value)>::max())
1175 {
1176 throw std::out_of_range("Out of range");
1177 }
1178 value = static_cast<decltype(value)>(tmp);
1179 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001180 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301181 {
1182 log<level::ERR>("Exception for RememberOldPasswordTimes",
1183 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001184 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301185 }
1186 AccountPolicyIface::rememberOldPasswordTimes(value);
1187 }
1188 valueStr.clear();
1189 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1190 {
1191 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1192 }
1193 else
1194 {
1195 uint16_t value16 = 0;
1196 try
1197 {
1198 tmp = std::stoul(valueStr, nullptr);
1199 if (tmp > std::numeric_limits<decltype(value16)>::max())
1200 {
1201 throw std::out_of_range("Out of range");
1202 }
1203 value16 = static_cast<decltype(value16)>(tmp);
1204 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001205 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301206 {
1207 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1208 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001209 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301210 }
1211 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1212 }
1213 valueStr.clear();
1214 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1215 {
1216 AccountPolicyIface::accountUnlockTimeout(0);
1217 }
1218 else
1219 {
1220 uint32_t value32 = 0;
1221 try
1222 {
1223 tmp = std::stoul(valueStr, nullptr);
1224 if (tmp > std::numeric_limits<decltype(value32)>::max())
1225 {
1226 throw std::out_of_range("Out of range");
1227 }
1228 value32 = static_cast<decltype(value32)>(tmp);
1229 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001230 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301231 {
1232 log<level::ERR>("Exception for AccountUnlockTimeout",
1233 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001234 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301235 }
1236 AccountPolicyIface::accountUnlockTimeout(value32);
1237 }
Nan Zhou4bc69812022-10-25 00:07:13 +00001238}
1239
1240void UserMgr::initUserObjects(void)
1241{
1242 // All user management lock has to be based on /etc/shadow
1243 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
1244 std::vector<std::string> userNameList;
1245 std::vector<std::string> sshGrpUsersList;
1246 UserSSHLists userSSHLists = getUserAndSshGrpList();
1247 userNameList = std::move(userSSHLists.first);
1248 sshGrpUsersList = std::move(userSSHLists.second);
1249
1250 if (!userNameList.empty())
1251 {
1252 std::map<std::string, std::vector<std::string>> groupLists;
1253 for (auto& grp : groupsMgr)
1254 {
1255 if (grp == grpSsh)
1256 {
1257 groupLists.emplace(grp, sshGrpUsersList);
1258 }
1259 else
1260 {
1261 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1262 groupLists.emplace(grp, grpUsersList);
1263 }
1264 }
1265 for (auto& grp : privMgr)
1266 {
1267 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1268 groupLists.emplace(grp, grpUsersList);
1269 }
1270
1271 for (auto& user : userNameList)
1272 {
1273 std::vector<std::string> userGroups;
1274 std::string userPriv;
1275 for (const auto& grp : groupLists)
1276 {
1277 std::vector<std::string> tempGrp = grp.second;
1278 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1279 tempGrp.end())
1280 {
1281 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1282 privMgr.end())
1283 {
1284 userPriv = grp.first;
1285 }
1286 else
1287 {
1288 userGroups.emplace_back(grp.first);
1289 }
1290 }
1291 }
1292 // Add user objects to the Users path.
1293 sdbusplus::message::object_path tempObjPath(usersObjPath);
1294 tempObjPath /= user;
1295 std::string objPath(tempObjPath);
1296 std::sort(userGroups.begin(), userGroups.end());
1297 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1298 bus, objPath.c_str(), userGroups,
1299 userPriv, isUserEnabled(user), *this));
1300 }
1301 }
1302}
1303
1304UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
1305 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1306 pamPasswdConfigFile(defaultPamPasswdConfigFile),
1307 pamAuthConfigFile(defaultPamAuthConfigFile)
1308{
1309 UserMgrIface::allPrivileges(privMgr);
1310 std::sort(groupsMgr.begin(), groupsMgr.end());
1311 UserMgrIface::allGroups(groupsMgr);
1312 initializeAccountPolicy();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301313 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301314
1315 // emit the signal
1316 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301317}
1318
Nan Zhou49c81362022-10-25 00:07:08 +00001319void UserMgr::executeUserAdd(const char* userName, const char* groups,
1320 bool sshRequested, bool enabled)
1321{
1322 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
1323 // 1970-01-01, that's an implementation-defined behavior
1324 executeCmd("/usr/sbin/useradd", userName, "-G", groups, "-m", "-N", "-s",
1325 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
1326 (enabled ? "" : "1970-01-01"));
1327}
1328
1329void UserMgr::executeUserDelete(const char* userName)
1330{
1331 executeCmd("/usr/sbin/userdel", userName, "-r");
1332}
1333
Nan Zhouf25443e2022-10-25 00:07:11 +00001334void UserMgr::executeUserRename(const char* userName, const char* newUserName)
1335{
1336 std::string newHomeDir = "/home/";
1337 newHomeDir += newUserName;
1338 executeCmd("/usr/sbin/usermod", "-l", newUserName, userName, "-d",
1339 newHomeDir.c_str(), "-m");
1340}
1341
Nan Zhoufef63032022-10-25 00:07:12 +00001342void UserMgr::executeUserModify(const char* userName, const char* newGroups,
1343 bool sshRequested)
1344{
1345 executeCmd("/usr/sbin/usermod", userName, "-G", newGroups, "-s",
1346 (sshRequested ? "/bin/sh" : "/bin/nologin"));
1347}
1348
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301349} // namespace user
1350} // namespace phosphor