blob: 2fd423ecc1734bcfe55ae4f5444ffa7f755e71f4 [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>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053034#include <boost/process/child.hpp>
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +053035#include <boost/process/io.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -060036#include <phosphor-logging/elog-errors.hpp>
37#include <phosphor-logging/elog.hpp>
38#include <phosphor-logging/log.hpp>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053039#include <xyz/openbmc_project/Common/error.hpp>
40#include <xyz/openbmc_project/User/Common/error.hpp>
Patrick Williams9638afb2021-02-22 17:16:24 -060041
42#include <algorithm>
43#include <fstream>
44#include <numeric>
45#include <regex>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053046
47namespace phosphor
48{
49namespace user
50{
51
Patrick Williams9638afb2021-02-22 17:16:24 -060052static constexpr const char* passwdFileName = "/etc/passwd";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053053static constexpr size_t ipmiMaxUsers = 15;
54static constexpr size_t ipmiMaxUserNameLen = 16;
55static constexpr size_t systemMaxUserNameLen = 30;
56static constexpr size_t maxSystemUsers = 30;
Patrick Williams9638afb2021-02-22 17:16:24 -060057static constexpr const char* grpSsh = "ssh";
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +053058static constexpr uint8_t minPasswdLength = 8;
59static 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";
70static constexpr const char* pamPasswdConfigFile = "/etc/pam.d/common-password";
71static constexpr const char* pamAuthConfigFile = "/etc/pam.d/common-auth";
Ratan Gupta7531e902021-09-21 20:53:16 +053072static constexpr const char* minLcaseCharsProp = "lcredit";
73static constexpr const char* minUcaseCharsProp = "ucredit";
74static constexpr const char* minDigitProp = "dcredit";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053075
Ratan Guptaaeaf9412019-02-11 04:41:52 -060076// Object Manager related
Patrick Williams9638afb2021-02-22 17:16:24 -060077static constexpr const char* ldapMgrObjBasePath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060078 "/xyz/openbmc_project/user/ldap";
79
80// Object Mapper related
Patrick Williams9638afb2021-02-22 17:16:24 -060081static constexpr const char* objMapperService =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060082 "xyz.openbmc_project.ObjectMapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060083static constexpr const char* objMapperPath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060084 "/xyz/openbmc_project/object_mapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060085static constexpr const char* objMapperInterface =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060086 "xyz.openbmc_project.ObjectMapper";
87
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053088using namespace phosphor::logging;
89using InsufficientPermission =
90 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
91using InternalFailure =
92 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
93using InvalidArgument =
94 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
95using UserNameExists =
96 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
97using UserNameDoesNotExist =
98 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
99using UserNameGroupFail =
100 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530101using NoResource =
102 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
103
104using Argument = xyz::openbmc_project::Common::InvalidArgument;
105
106template <typename... ArgTypes>
Patrick Williams9638afb2021-02-22 17:16:24 -0600107static std::vector<std::string> executeCmd(const char* path,
108 ArgTypes&&... tArgs)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530109{
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530110 std::vector<std::string> stdOutput;
111 boost::process::ipstream stdOutStream;
Patrick Williams9638afb2021-02-22 17:16:24 -0600112 boost::process::child execProg(path, const_cast<char*>(tArgs)...,
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530113 boost::process::std_out > stdOutStream);
114 std::string stdOutLine;
115
116 while (stdOutStream && std::getline(stdOutStream, stdOutLine) &&
117 !stdOutLine.empty())
118 {
119 stdOutput.emplace_back(stdOutLine);
120 }
121
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530122 execProg.wait();
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530123
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530124 int retCode = execProg.exit_code();
125 if (retCode)
126 {
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530127 log<level::ERR>("Command execution failed", entry("PATH=%d", path),
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530128 entry("RETURN_CODE:%d", retCode));
129 elog<InternalFailure>();
130 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530131
132 return stdOutput;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530133}
134
135static std::string getCSVFromVector(std::vector<std::string> vec)
136{
137 switch (vec.size())
138 {
139 case 0:
140 {
141 return "";
142 }
143 break;
144
145 case 1:
146 {
147 return std::string{vec[0]};
148 }
149 break;
150
151 default:
152 {
153 return std::accumulate(
154 std::next(vec.begin()), vec.end(), vec[0],
155 [](std::string a, std::string b) { return a + ',' + b; });
156 }
157 }
158}
159
Patrick Williams9638afb2021-02-22 17:16:24 -0600160static bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530161{
162 std::string::size_type delStrPos = csvStr.find(delStr);
163 if (delStrPos != std::string::npos)
164 {
165 // need to also delete the comma char
166 if (delStrPos == 0)
167 {
168 csvStr.erase(delStrPos, delStr.size() + 1);
169 }
170 else
171 {
172 csvStr.erase(delStrPos - 1, delStr.size() + 1);
173 }
174 return true;
175 }
176 return false;
177}
178
Patrick Williams9638afb2021-02-22 17:16:24 -0600179bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530180{
181 if (userName.empty())
182 {
183 log<level::ERR>("User name is empty");
184 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
185 Argument::ARGUMENT_VALUE("Null"));
186 }
187 if (usersList.find(userName) == usersList.end())
188 {
189 return false;
190 }
191 return true;
192}
193
Patrick Williams9638afb2021-02-22 17:16:24 -0600194void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530195{
196 if (isUserExist(userName) == false)
197 {
198 log<level::ERR>("User does not exist",
199 entry("USER_NAME=%s", userName.c_str()));
200 elog<UserNameDoesNotExist>();
201 }
202}
203
Patrick Williams9638afb2021-02-22 17:16:24 -0600204void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530205{
206 if (isUserExist(userName) == true)
207 {
208 log<level::ERR>("User already exists",
209 entry("USER_NAME=%s", userName.c_str()));
210 elog<UserNameExists>();
211 }
212}
213
214void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600215 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530216{
217 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
218 groupNames.end())
219 {
220 if (userName.length() > ipmiMaxUserNameLen)
221 {
222 log<level::ERR>("IPMI user name length limitation",
223 entry("SIZE=%d", userName.length()));
224 elog<UserNameGroupFail>(
225 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
226 "IPMI length"));
227 }
228 }
229 if (userName.length() > systemMaxUserNameLen)
230 {
231 log<level::ERR>("User name length limitation",
232 entry("SIZE=%d", userName.length()));
233 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
234 Argument::ARGUMENT_VALUE("Invalid length"));
235 }
236 if (!std::regex_match(userName.c_str(),
237 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
238 {
239 log<level::ERR>("Invalid user name",
240 entry("USER_NAME=%s", userName.c_str()));
241 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
242 Argument::ARGUMENT_VALUE("Invalid data"));
243 }
244}
245
246void UserMgr::throwForMaxGrpUserCount(
Patrick Williams9638afb2021-02-22 17:16:24 -0600247 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530248{
249 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
250 groupNames.end())
251 {
252 if (getIpmiUsersCount() >= ipmiMaxUsers)
253 {
254 log<level::ERR>("IPMI user limit reached");
255 elog<NoResource>(
256 xyz::openbmc_project::User::Common::NoResource::REASON(
257 "ipmi user count reached"));
258 }
259 }
260 else
261 {
262 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
263 (maxSystemUsers - ipmiMaxUsers))
264 {
265 log<level::ERR>("Non-ipmi User limit reached");
266 elog<NoResource>(
267 xyz::openbmc_project::User::Common::NoResource::REASON(
268 "Non-ipmi user count reached"));
269 }
270 }
271 return;
272}
273
Patrick Williams9638afb2021-02-22 17:16:24 -0600274void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530275{
276 if (!priv.empty() &&
277 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
278 {
279 log<level::ERR>("Invalid privilege");
280 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
281 Argument::ARGUMENT_VALUE(priv.c_str()));
282 }
283}
284
Patrick Williams9638afb2021-02-22 17:16:24 -0600285void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530286{
Patrick Williams9638afb2021-02-22 17:16:24 -0600287 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530288 {
289 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
290 groupsMgr.end())
291 {
292 log<level::ERR>("Invalid Group Name listed");
293 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
294 Argument::ARGUMENT_VALUE(group.c_str()));
295 }
296 }
297}
298
299void UserMgr::createUser(std::string userName,
300 std::vector<std::string> groupNames, std::string priv,
301 bool enabled)
302{
303 throwForInvalidPrivilege(priv);
304 throwForInvalidGroups(groupNames);
305 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500306 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530307 throwForUserExists(userName);
308 throwForUserNameConstraints(userName, groupNames);
309 throwForMaxGrpUserCount(groupNames);
310
311 std::string groups = getCSVFromVector(groupNames);
312 bool sshRequested = removeStringFromCSV(groups, grpSsh);
313
314 // treat privilege as a group - This is to avoid using different file to
315 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530316 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530317 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530318 if (groups.size() != 0)
319 {
320 groups += ",";
321 }
322 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530323 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530324 try
325 {
326 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530327 "-m", "-N", "-s",
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530328 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
329 (enabled ? "" : "1970-01-02"));
330 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600331 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530332 {
333 log<level::ERR>("Unable to create new user");
334 elog<InternalFailure>();
335 }
336
337 // Add the users object before sending out the signal
338 std::string userObj = std::string(usersObjPath) + "/" + userName;
339 std::sort(groupNames.begin(), groupNames.end());
340 usersList.emplace(
341 userName, std::move(std::make_unique<phosphor::user::Users>(
342 bus, userObj.c_str(), groupNames, priv, enabled, *this)));
343
344 log<level::INFO>("User created successfully",
345 entry("USER_NAME=%s", userName.c_str()));
346 return;
347}
348
349void UserMgr::deleteUser(std::string userName)
350{
351 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500352 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530353 throwForUserDoesNotExist(userName);
354 try
355 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530356 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530357 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600358 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530359 {
360 log<level::ERR>("User delete failed",
361 entry("USER_NAME=%s", userName.c_str()));
362 elog<InternalFailure>();
363 }
364
365 usersList.erase(userName);
366
367 log<level::INFO>("User deleted successfully",
368 entry("USER_NAME=%s", userName.c_str()));
369 return;
370}
371
372void UserMgr::renameUser(std::string userName, std::string newUserName)
373{
374 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500375 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530376 throwForUserDoesNotExist(userName);
377 throwForUserExists(newUserName);
378 throwForUserNameConstraints(newUserName,
379 usersList[userName].get()->userGroups());
380 try
381 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530382 std::string newHomeDir = "/home/" + newUserName;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530383 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530384 userName.c_str(), "-d", newHomeDir.c_str(), "-m");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530385 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600386 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530387 {
388 log<level::ERR>("User rename failed",
389 entry("USER_NAME=%s", userName.c_str()));
390 elog<InternalFailure>();
391 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600392 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530393 std::string priv = user.get()->userPrivilege();
394 std::vector<std::string> groupNames = user.get()->userGroups();
395 bool enabled = user.get()->userEnabled();
396 std::string newUserObj = std::string(usersObjPath) + "/" + newUserName;
397 // Special group 'ipmi' needs a way to identify user renamed, in order to
398 // update encrypted password. It can't rely only on InterfacesRemoved &
399 // InterfacesAdded. So first send out userRenamed signal.
400 this->userRenamed(userName, newUserName);
401 usersList.erase(userName);
402 usersList.emplace(
403 newUserName,
404 std::move(std::make_unique<phosphor::user::Users>(
405 bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
406 return;
407}
408
Patrick Williams9638afb2021-02-22 17:16:24 -0600409void UserMgr::updateGroupsAndPriv(const std::string& userName,
410 const std::vector<std::string>& groupNames,
411 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530412{
413 throwForInvalidPrivilege(priv);
414 throwForInvalidGroups(groupNames);
415 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500416 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530417 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600418 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530419 usersList[userName].get()->userGroups();
420 std::vector<std::string> groupDiff;
421 // Note: already dealing with sorted group lists.
422 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
423 groupNames.begin(), groupNames.end(),
424 std::back_inserter(groupDiff));
425 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
426 groupDiff.end())
427 {
428 throwForUserNameConstraints(userName, groupNames);
429 throwForMaxGrpUserCount(groupNames);
430 }
431
432 std::string groups = getCSVFromVector(groupNames);
433 bool sshRequested = removeStringFromCSV(groups, grpSsh);
434
435 // treat privilege as a group - This is to avoid using different file to
436 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530437 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530438 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530439 if (groups.size() != 0)
440 {
441 groups += ",";
442 }
443 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530444 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530445 try
446 {
447 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
448 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
449 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600450 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530451 {
452 log<level::ERR>("Unable to modify user privilege / groups");
453 elog<InternalFailure>();
454 }
455
456 log<level::INFO>("User groups / privilege updated successfully",
457 entry("USER_NAME=%s", userName.c_str()));
458 return;
459}
460
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530461uint8_t UserMgr::minPasswordLength(uint8_t value)
462{
463 if (value == AccountPolicyIface::minPasswordLength())
464 {
465 return value;
466 }
467 if (value < minPasswdLength)
468 {
469 return value;
470 }
471 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
472 std::to_string(value)) != success)
473 {
474 log<level::ERR>("Unable to set minPasswordLength");
475 elog<InternalFailure>();
476 }
477 return AccountPolicyIface::minPasswordLength(value);
478}
479
480uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
481{
482 if (value == AccountPolicyIface::rememberOldPasswordTimes())
483 {
484 return value;
485 }
486 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
487 std::to_string(value)) != success)
488 {
489 log<level::ERR>("Unable to set rememberOldPasswordTimes");
490 elog<InternalFailure>();
491 }
492 return AccountPolicyIface::rememberOldPasswordTimes(value);
493}
494
495uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
496{
497 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
498 {
499 return value;
500 }
501 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
502 std::to_string(value)) != success)
503 {
504 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
505 elog<InternalFailure>();
506 }
507 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
508}
509
510uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
511{
512 if (value == AccountPolicyIface::accountUnlockTimeout())
513 {
514 return value;
515 }
516 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
517 success)
518 {
519 log<level::ERR>("Unable to set accountUnlockTimeout");
520 elog<InternalFailure>();
521 }
522 return AccountPolicyIface::accountUnlockTimeout(value);
523}
524
Patrick Williams9638afb2021-02-22 17:16:24 -0600525int UserMgr::getPamModuleArgValue(const std::string& moduleName,
526 const std::string& argName,
527 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530528{
529 std::string fileName;
530 if (moduleName == pamTally2)
531 {
532 fileName = pamAuthConfigFile;
533 }
534 else
535 {
536 fileName = pamPasswdConfigFile;
537 }
538 std::ifstream fileToRead(fileName, std::ios::in);
539 if (!fileToRead.is_open())
540 {
541 log<level::ERR>("Failed to open pam configuration file",
542 entry("FILE_NAME=%s", fileName.c_str()));
543 return failure;
544 }
545 std::string line;
546 auto argSearch = argName + "=";
547 size_t startPos = 0;
548 size_t endPos = 0;
549 while (getline(fileToRead, line))
550 {
551 // skip comments section starting with #
552 if ((startPos = line.find('#')) != std::string::npos)
553 {
554 if (startPos == 0)
555 {
556 continue;
557 }
558 // skip comments after meaningful section and process those
559 line = line.substr(0, startPos);
560 }
561 if (line.find(moduleName) != std::string::npos)
562 {
563 if ((startPos = line.find(argSearch)) != std::string::npos)
564 {
565 if ((endPos = line.find(' ', startPos)) == std::string::npos)
566 {
567 endPos = line.size();
568 }
569 startPos += argSearch.size();
570 argValue = line.substr(startPos, endPos - startPos);
571 return success;
572 }
573 }
574 }
575 return failure;
576}
577
Patrick Williams9638afb2021-02-22 17:16:24 -0600578int UserMgr::setPamModuleArgValue(const std::string& moduleName,
579 const std::string& argName,
580 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530581{
582 std::string fileName;
583 if (moduleName == pamTally2)
584 {
585 fileName = pamAuthConfigFile;
586 }
587 else
588 {
589 fileName = pamPasswdConfigFile;
590 }
591 std::string tmpFileName = fileName + "_tmp";
592 std::ifstream fileToRead(fileName, std::ios::in);
593 std::ofstream fileToWrite(tmpFileName, std::ios::out);
594 if (!fileToRead.is_open() || !fileToWrite.is_open())
595 {
596 log<level::ERR>("Failed to open pam configuration /tmp file",
597 entry("FILE_NAME=%s", fileName.c_str()));
598 return failure;
599 }
600 std::string line;
601 auto argSearch = argName + "=";
602 size_t startPos = 0;
603 size_t endPos = 0;
604 bool found = false;
605 while (getline(fileToRead, line))
606 {
607 // skip comments section starting with #
608 if ((startPos = line.find('#')) != std::string::npos)
609 {
610 if (startPos == 0)
611 {
612 fileToWrite << line << std::endl;
613 continue;
614 }
615 // skip comments after meaningful section and process those
616 line = line.substr(0, startPos);
617 }
618 if (line.find(moduleName) != std::string::npos)
619 {
620 if ((startPos = line.find(argSearch)) != std::string::npos)
621 {
622 if ((endPos = line.find(' ', startPos)) == std::string::npos)
623 {
624 endPos = line.size();
625 }
626 startPos += argSearch.size();
627 fileToWrite << line.substr(0, startPos) << argValue
628 << line.substr(endPos, line.size() - endPos)
629 << std::endl;
630 found = true;
631 continue;
632 }
633 }
634 fileToWrite << line << std::endl;
635 }
636 fileToWrite.close();
637 fileToRead.close();
638 if (found)
639 {
640 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
641 {
642 return success;
643 }
644 }
645 return failure;
646}
647
Patrick Williams9638afb2021-02-22 17:16:24 -0600648void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530649{
650 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500651 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530652 throwForUserDoesNotExist(userName);
653 try
654 {
655 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
656 (enabled ? "" : "1970-01-02"));
657 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600658 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530659 {
660 log<level::ERR>("Unable to modify user enabled state");
661 elog<InternalFailure>();
662 }
663
664 log<level::INFO>("User enabled/disabled state updated successfully",
665 entry("USER_NAME=%s", userName.c_str()),
666 entry("ENABLED=%d", enabled));
667 return;
668}
669
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530670/**
671 * pam_tally2 app will provide the user failure count and failure status
672 * in second line of output with words position [0] - user name,
673 * [1] - failure count, [2] - latest timestamp, [3] - failure timestamp
674 * [4] - failure app
675 **/
676
677static constexpr size_t t2UserIdx = 0;
678static constexpr size_t t2FailCntIdx = 1;
679static constexpr size_t t2OutputIndex = 1;
680
Patrick Williams9638afb2021-02-22 17:16:24 -0600681bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530682{
683 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500684 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530685 std::vector<std::string> output;
686
687 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
688
689 std::vector<std::string> splitWords;
690 boost::algorithm::split(splitWords, output[t2OutputIndex],
691 boost::algorithm::is_any_of("\t "),
692 boost::token_compress_on);
693
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530694 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530695 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530696 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
697 uint16_t value16 = 0;
698 if (tmp > std::numeric_limits<decltype(value16)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530699 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530700 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530701 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530702 value16 = static_cast<decltype(value16)>(tmp);
703 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 &&
704 value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530705 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530706 return true; // User account is locked out
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530707 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530708 return false; // User account is un-locked
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530709 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600710 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530711 {
712 log<level::ERR>("Exception for userLockedForFailedAttempt",
713 entry("WHAT=%s", e.what()));
714 throw;
715 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530716}
717
Patrick Williams9638afb2021-02-22 17:16:24 -0600718bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
719 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530720{
721 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500722 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530723 std::vector<std::string> output;
724 if (value == true)
725 {
726 return userLockedForFailedAttempt(userName);
727 }
728 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
729
730 std::vector<std::string> splitWords;
731 boost::algorithm::split(splitWords, output[t2OutputIndex],
732 boost::algorithm::is_any_of("\t "),
733 boost::token_compress_on);
734
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530735 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530736}
737
Patrick Williams9638afb2021-02-22 17:16:24 -0600738bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600739{
740 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500741 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600742
743 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600744 {};
745 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600746 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
747 if (buflen < -1)
748 {
749 // Use a default size if there is no hard limit suggested by sysconf()
750 buflen = 1024;
751 }
752 std::vector<char> buffer(buflen);
753 auto status =
754 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
755 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
756 // If no matching password record was found, these functions return 0
757 // and store NULL in *spwdPtr
758 if ((status == 0) && (&spwd == spwdPtr))
759 {
760 // Determine password validity per "chage" docs, where:
761 // spwd.sp_lstchg == 0 means password is expired, and
762 // spwd.sp_max == -1 means the password does not expire.
763 constexpr long seconds_per_day = 60 * 60 * 24;
764 long today = static_cast<long>(time(NULL)) / seconds_per_day;
765 if ((spwd.sp_lstchg == 0) ||
766 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
767 {
768 return true;
769 }
770 }
771 else
772 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000773 // User entry is missing in /etc/shadow, indicating no SHA password.
774 // Treat this as new user without password entry in /etc/shadow
775 // TODO: Add property to indicate user password was not set yet
776 // https://github.com/openbmc/phosphor-user-manager/issues/8
777 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600778 }
779
780 return false;
781}
782
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530783UserSSHLists UserMgr::getUserAndSshGrpList()
784{
785 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500786 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530787
788 std::vector<std::string> userList;
789 std::vector<std::string> sshUsersList;
790 struct passwd pw, *pwp = nullptr;
791 std::array<char, 1024> buffer{};
792
793 phosphor::user::File passwd(passwdFileName, "r");
794 if ((passwd)() == NULL)
795 {
796 log<level::ERR>("Error opening the passwd file");
797 elog<InternalFailure>();
798 }
799
800 while (true)
801 {
802 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
803 &pwp);
804 if ((r != 0) || (pwp == NULL))
805 {
806 // Any error, break the loop.
807 break;
808 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530809#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530810 // Add all users whose UID >= 1000 and < 65534
811 // and special UID 0.
812 if ((pwp->pw_uid == 0) ||
813 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530814#else
815 // Add all users whose UID >=1000 and < 65534
816 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
817#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530818 {
819 std::string userName(pwp->pw_name);
820 userList.emplace_back(userName);
821
822 // ssh doesn't have separate group. Check login shell entry to
823 // get all users list which are member of ssh group.
824 std::string loginShell(pwp->pw_shell);
825 if (loginShell == "/bin/sh")
826 {
827 sshUsersList.emplace_back(userName);
828 }
829 }
830 }
831 endpwent();
832 return std::make_pair(std::move(userList), std::move(sshUsersList));
833}
834
835size_t UserMgr::getIpmiUsersCount()
836{
837 std::vector<std::string> userList = getUsersInGroup("ipmi");
838 return userList.size();
839}
840
Patrick Williams9638afb2021-02-22 17:16:24 -0600841bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530842{
843 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500844 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530845 std::array<char, 4096> buffer{};
846 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600847 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530848 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
849 buffer.max_size(), &resultPtr);
850 if (!status && (&spwd == resultPtr))
851 {
852 if (resultPtr->sp_expire >= 0)
853 {
854 return false; // user locked out
855 }
856 return true;
857 }
858 return false; // assume user is disabled for any error.
859}
860
Patrick Williams9638afb2021-02-22 17:16:24 -0600861std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530862{
863 std::vector<std::string> usersInGroup;
864 // Should be more than enough to get the pwd structure.
865 std::array<char, 4096> buffer{};
866 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600867 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530868
869 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
870 buffer.max_size(), &resultPtr);
871
872 if (!status && (&grp == resultPtr))
873 {
874 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
875 {
876 usersInGroup.emplace_back(*(grp.gr_mem));
877 }
878 }
879 else
880 {
881 log<level::ERR>("Group not found",
882 entry("GROUP=%s", groupName.c_str()));
883 // Don't throw error, just return empty userList - fallback
884 }
885 return usersInGroup;
886}
887
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600888DbusUserObj UserMgr::getPrivilegeMapperObject(void)
889{
890 DbusUserObj objects;
891 try
892 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500893 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
894 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600895
896 auto ldapMgmtService =
897 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600898 auto method = bus.new_method_call(
899 ldapMgmtService.c_str(), ldapMgrObjBasePath,
900 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
901
902 auto reply = bus.call(method);
903 reply.read(objects);
904 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600905 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600906 {
907 log<level::ERR>("Unable to get the User Service",
908 entry("WHAT=%s", e.what()));
909 throw;
910 }
Patrick Williams178c3f62021-09-02 09:50:31 -0500911 catch (const sdbusplus::exception::exception& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600912 {
913 log<level::ERR>(
914 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
915 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
916 throw;
917 }
918 return objects;
919}
920
Patrick Williams9638afb2021-02-22 17:16:24 -0600921std::string UserMgr::getLdapGroupName(const std::string& userName)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600922{
923 struct passwd pwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600924 {};
925 struct passwd* pwdPtr = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600926 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
927 if (buflen < -1)
928 {
929 // Use a default size if there is no hard limit suggested by sysconf()
930 buflen = 1024;
931 }
932 std::vector<char> buffer(buflen);
933 gid_t gid = 0;
934
935 auto status =
936 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
937 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
938 // If no matching password record was found, these functions return 0
939 // and store NULL in *pwdPtr
940 if (!status && (&pwd == pwdPtr))
941 {
942 gid = pwd.pw_gid;
943 }
944 else
945 {
946 log<level::ERR>("User does not exist",
947 entry("USER_NAME=%s", userName.c_str()));
948 elog<UserNameDoesNotExist>();
949 }
950
Patrick Williams9638afb2021-02-22 17:16:24 -0600951 struct group* groups = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600952 std::string ldapGroupName;
953
954 while ((groups = getgrent()) != NULL)
955 {
956 if (groups->gr_gid == gid)
957 {
958 ldapGroupName = groups->gr_name;
959 break;
960 }
961 }
962 // Call endgrent() to close the group database.
963 endgrent();
964
965 return ldapGroupName;
966}
967
Patrick Williams9638afb2021-02-22 17:16:24 -0600968std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600969{
970 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
971 objMapperInterface, "GetObject");
972
973 mapperCall.append(std::move(path));
974 mapperCall.append(std::vector<std::string>({std::move(intf)}));
975
976 auto mapperResponseMsg = bus.call(mapperCall);
977
978 if (mapperResponseMsg.is_method_error())
979 {
980 log<level::ERR>("Error in mapper call");
981 elog<InternalFailure>();
982 }
983
984 std::map<std::string, std::vector<std::string>> mapperResponse;
985 mapperResponseMsg.read(mapperResponse);
986
987 if (mapperResponse.begin() == mapperResponse.end())
988 {
989 log<level::ERR>("Invalid response from mapper");
990 elog<InternalFailure>();
991 }
992
993 return mapperResponse.begin()->first;
994}
995
996UserInfoMap UserMgr::getUserInfo(std::string userName)
997{
998 UserInfoMap userInfo;
999 // Check whether the given user is local user or not.
1000 if (isUserExist(userName) == true)
1001 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001002 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001003 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1004 userInfo.emplace("UserGroups", user.get()->userGroups());
1005 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1006 userInfo.emplace("UserLockedForFailedAttempt",
1007 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001008 userInfo.emplace("UserPasswordExpired",
1009 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001010 userInfo.emplace("RemoteUser", false);
1011 }
1012 else
1013 {
1014 std::string ldapGroupName = getLdapGroupName(userName);
1015 if (ldapGroupName.empty())
1016 {
1017 log<level::ERR>("Unable to get group name",
1018 entry("USER_NAME=%s", userName.c_str()));
1019 elog<InternalFailure>();
1020 }
1021
1022 DbusUserObj objects = getPrivilegeMapperObject();
1023
1024 std::string privilege;
1025 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001026 std::string ldapConfigPath;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001027
1028 try
1029 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001030 for (const auto& obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001031 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001032 for (const auto& interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001033 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001034 if ((interface.first ==
1035 "xyz.openbmc_project.Object.Enable"))
1036 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001037 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001038 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001039 auto value = std::get<bool>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001040 if ((property.first == "Enabled") &&
1041 (value == true))
1042 {
1043 ldapConfigPath = obj.first;
1044 break;
1045 }
1046 }
1047 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001048 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001049 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001050 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001051 break;
1052 }
1053 }
1054
1055 if (ldapConfigPath.empty())
1056 {
1057 return userInfo;
1058 }
1059
Patrick Williams9638afb2021-02-22 17:16:24 -06001060 for (const auto& obj : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001061 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001062 for (const auto& interface : obj.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001063 {
1064 if ((interface.first ==
1065 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1066 (obj.first.str.find(ldapConfigPath) !=
1067 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001068 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001069
Patrick Williams9638afb2021-02-22 17:16:24 -06001070 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001071 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001072 auto value = std::get<std::string>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001073 if (property.first == "GroupName")
1074 {
1075 groupName = value;
1076 }
1077 else if (property.first == "Privilege")
1078 {
1079 privilege = value;
1080 }
1081 if (groupName == ldapGroupName)
1082 {
1083 userInfo["UserPrivilege"] = privilege;
1084 }
1085 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001086 }
1087 }
1088 }
1089 auto priv = std::get<std::string>(userInfo["UserPrivilege"]);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001090
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001091 if (priv.empty())
1092 {
1093 log<level::ERR>("LDAP group privilege mapping does not exist");
1094 }
1095 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001096 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001097 {
1098 log<level::ERR>("Error while accessing variant",
1099 entry("WHAT=%s", e.what()));
1100 elog<InternalFailure>();
1101 }
1102 userInfo.emplace("RemoteUser", true);
1103 }
1104
1105 return userInfo;
1106}
1107
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301108void UserMgr::initUserObjects(void)
1109{
1110 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001111 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301112 std::vector<std::string> userNameList;
1113 std::vector<std::string> sshGrpUsersList;
1114 UserSSHLists userSSHLists = getUserAndSshGrpList();
1115 userNameList = std::move(userSSHLists.first);
1116 sshGrpUsersList = std::move(userSSHLists.second);
1117
1118 if (!userNameList.empty())
1119 {
1120 std::map<std::string, std::vector<std::string>> groupLists;
Patrick Williams9638afb2021-02-22 17:16:24 -06001121 for (auto& grp : groupsMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301122 {
1123 if (grp == grpSsh)
1124 {
1125 groupLists.emplace(grp, sshGrpUsersList);
1126 }
1127 else
1128 {
1129 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1130 groupLists.emplace(grp, grpUsersList);
1131 }
1132 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001133 for (auto& grp : privMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301134 {
1135 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1136 groupLists.emplace(grp, grpUsersList);
1137 }
1138
Patrick Williams9638afb2021-02-22 17:16:24 -06001139 for (auto& user : userNameList)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301140 {
1141 std::vector<std::string> userGroups;
1142 std::string userPriv;
Patrick Williams9638afb2021-02-22 17:16:24 -06001143 for (const auto& grp : groupLists)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301144 {
1145 std::vector<std::string> tempGrp = grp.second;
1146 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1147 tempGrp.end())
1148 {
1149 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1150 privMgr.end())
1151 {
1152 userPriv = grp.first;
1153 }
1154 else
1155 {
1156 userGroups.emplace_back(grp.first);
1157 }
1158 }
1159 }
1160 // Add user objects to the Users path.
1161 auto objPath = std::string(usersObjPath) + "/" + user;
1162 std::sort(userGroups.begin(), userGroups.end());
1163 usersList.emplace(user,
1164 std::move(std::make_unique<phosphor::user::Users>(
1165 bus, objPath.c_str(), userGroups, userPriv,
1166 isUserEnabled(user), *this)));
1167 }
1168 }
1169}
1170
Patrick Williams9638afb2021-02-22 17:16:24 -06001171UserMgr::UserMgr(sdbusplus::bus::bus& bus, const char* path) :
Ratan Gupta1af12232018-11-03 00:35:38 +05301172 Ifaces(bus, path, true), bus(bus), path(path)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301173{
1174 UserMgrIface::allPrivileges(privMgr);
1175 std::sort(groupsMgr.begin(), groupsMgr.end());
1176 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301177 std::string valueStr;
Ratan Gupta7531e902021-09-21 20:53:16 +05301178 auto pwdLength = minPasswdLength;
1179 auto value = 0;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301180 unsigned long tmp = 0;
1181 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1182 success)
1183 {
1184 AccountPolicyIface::minPasswordLength(minPasswdLength);
1185 }
1186 else
1187 {
1188 try
1189 {
1190 tmp = std::stoul(valueStr, nullptr);
1191 if (tmp > std::numeric_limits<decltype(value)>::max())
1192 {
1193 throw std::out_of_range("Out of range");
1194 }
Ratan Gupta7531e902021-09-21 20:53:16 +05301195 pwdLength = static_cast<decltype(pwdLength)>(tmp);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301196 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001197 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301198 {
1199 log<level::ERR>("Exception for MinPasswordLength",
1200 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001201 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301202 }
Ratan Gupta7531e902021-09-21 20:53:16 +05301203 AccountPolicyIface::minPasswordLength(pwdLength);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301204 }
1205 valueStr.clear();
1206 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1207 success)
1208 {
1209 AccountPolicyIface::rememberOldPasswordTimes(0);
1210 }
1211 else
1212 {
1213 value = 0;
1214 try
1215 {
1216 tmp = std::stoul(valueStr, nullptr);
1217 if (tmp > std::numeric_limits<decltype(value)>::max())
1218 {
1219 throw std::out_of_range("Out of range");
1220 }
1221 value = static_cast<decltype(value)>(tmp);
1222 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001223 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301224 {
1225 log<level::ERR>("Exception for RememberOldPasswordTimes",
1226 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001227 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301228 }
1229 AccountPolicyIface::rememberOldPasswordTimes(value);
1230 }
1231 valueStr.clear();
1232 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1233 {
1234 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1235 }
1236 else
1237 {
1238 uint16_t value16 = 0;
1239 try
1240 {
1241 tmp = std::stoul(valueStr, nullptr);
1242 if (tmp > std::numeric_limits<decltype(value16)>::max())
1243 {
1244 throw std::out_of_range("Out of range");
1245 }
1246 value16 = static_cast<decltype(value16)>(tmp);
1247 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001248 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301249 {
1250 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1251 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001252 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301253 }
1254 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1255 }
1256 valueStr.clear();
1257 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1258 {
1259 AccountPolicyIface::accountUnlockTimeout(0);
1260 }
1261 else
1262 {
1263 uint32_t value32 = 0;
1264 try
1265 {
1266 tmp = std::stoul(valueStr, nullptr);
1267 if (tmp > std::numeric_limits<decltype(value32)>::max())
1268 {
1269 throw std::out_of_range("Out of range");
1270 }
1271 value32 = static_cast<decltype(value32)>(tmp);
1272 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001273 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301274 {
1275 log<level::ERR>("Exception for AccountUnlockTimeout",
1276 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001277 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301278 }
1279 AccountPolicyIface::accountUnlockTimeout(value32);
1280 }
Ratan Gupta7531e902021-09-21 20:53:16 +05301281
1282 // Don't consume all the configuratios if the sum of all other
1283 // chars is greater then the min password length.
1284 if (pwdLength > (MIN_LCASE_CHRS + MIN_UCASE_CHRS + MIN_DIGITS))
1285 {
1286 int val =
1287 (MIN_LCASE_CHRS * (-1)); // value shoukd be in negative to tell the
1288 // minimum number of digits required
1289 if (val > 0 || MIN_LCASE_CHRS > std::numeric_limits<uint8_t>::max() ||
1290 setPamModuleArgValue(pamCrackLib, minLcaseCharsProp,
1291 std::to_string(val)) != success)
1292 {
1293 log<level::ERR>("Unable to set minLowercase characters",
1294 entry("MIN_LCASE_CHRS=%d", MIN_LCASE_CHRS));
1295 }
1296 val = (MIN_UCASE_CHRS * (-1));
1297
1298 if (val > 0 || MIN_UCASE_CHRS > std::numeric_limits<uint8_t>::max() ||
1299 setPamModuleArgValue(pamCrackLib, minUcaseCharsProp,
1300 std::to_string(val)) != success)
1301 {
1302 log<level::ERR>("Unable to set minUppercase characters",
1303 entry("MIN_UCASE_CHRS=%d", MIN_UCASE_CHRS));
1304 }
1305 val = (MIN_DIGITS * (-1));
1306 if (val > 0 || MIN_DIGITS > std::numeric_limits<uint8_t>::max() ||
1307 setPamModuleArgValue(pamCrackLib, minDigitProp,
1308 std::to_string(val)) != success)
1309 {
1310 log<level::ERR>("Unable to set mindigit",
1311 entry("MIN_DIGITS=%d", MIN_DIGITS));
1312 }
1313 }
1314 else
1315 {
1316
1317 log<level::ERR>("Min password length should be >= sum of "
1318 "lowercase, uppercase, digits");
1319 }
1320
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301321 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301322
1323 // emit the signal
1324 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301325}
1326
1327} // namespace user
1328} // namespace phosphor