blob: d5100af0b426bdadce11455790d0613566834644 [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";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053072
Ratan Guptaaeaf9412019-02-11 04:41:52 -060073// Object Manager related
Patrick Williams9638afb2021-02-22 17:16:24 -060074static constexpr const char* ldapMgrObjBasePath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060075 "/xyz/openbmc_project/user/ldap";
76
77// Object Mapper related
Patrick Williams9638afb2021-02-22 17:16:24 -060078static constexpr const char* objMapperService =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060079 "xyz.openbmc_project.ObjectMapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060080static constexpr const char* objMapperPath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060081 "/xyz/openbmc_project/object_mapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060082static constexpr const char* objMapperInterface =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060083 "xyz.openbmc_project.ObjectMapper";
84
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053085using namespace phosphor::logging;
86using InsufficientPermission =
87 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
88using InternalFailure =
89 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
90using InvalidArgument =
91 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
92using UserNameExists =
93 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
94using UserNameDoesNotExist =
95 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
96using UserNameGroupFail =
97 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053098using NoResource =
99 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
100
101using Argument = xyz::openbmc_project::Common::InvalidArgument;
102
103template <typename... ArgTypes>
Patrick Williams9638afb2021-02-22 17:16:24 -0600104static std::vector<std::string> executeCmd(const char* path,
105 ArgTypes&&... tArgs)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530106{
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530107 std::vector<std::string> stdOutput;
108 boost::process::ipstream stdOutStream;
Patrick Williams9638afb2021-02-22 17:16:24 -0600109 boost::process::child execProg(path, const_cast<char*>(tArgs)...,
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530110 boost::process::std_out > stdOutStream);
111 std::string stdOutLine;
112
113 while (stdOutStream && std::getline(stdOutStream, stdOutLine) &&
114 !stdOutLine.empty())
115 {
116 stdOutput.emplace_back(stdOutLine);
117 }
118
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530119 execProg.wait();
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530120
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530121 int retCode = execProg.exit_code();
122 if (retCode)
123 {
Jonathan Domanccd28892021-10-14 16:43:33 -0700124 log<level::ERR>("Command execution failed", entry("PATH=%s", path),
125 entry("RETURN_CODE=%d", retCode));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530126 elog<InternalFailure>();
127 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530128
129 return stdOutput;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530130}
131
132static std::string getCSVFromVector(std::vector<std::string> vec)
133{
134 switch (vec.size())
135 {
136 case 0:
137 {
138 return "";
139 }
140 break;
141
142 case 1:
143 {
144 return std::string{vec[0]};
145 }
146 break;
147
148 default:
149 {
150 return std::accumulate(
151 std::next(vec.begin()), vec.end(), vec[0],
152 [](std::string a, std::string b) { return a + ',' + b; });
153 }
154 }
155}
156
Patrick Williams9638afb2021-02-22 17:16:24 -0600157static bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530158{
159 std::string::size_type delStrPos = csvStr.find(delStr);
160 if (delStrPos != std::string::npos)
161 {
162 // need to also delete the comma char
163 if (delStrPos == 0)
164 {
165 csvStr.erase(delStrPos, delStr.size() + 1);
166 }
167 else
168 {
169 csvStr.erase(delStrPos - 1, delStr.size() + 1);
170 }
171 return true;
172 }
173 return false;
174}
175
Patrick Williams9638afb2021-02-22 17:16:24 -0600176bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530177{
178 if (userName.empty())
179 {
180 log<level::ERR>("User name is empty");
181 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
182 Argument::ARGUMENT_VALUE("Null"));
183 }
184 if (usersList.find(userName) == usersList.end())
185 {
186 return false;
187 }
188 return true;
189}
190
Patrick Williams9638afb2021-02-22 17:16:24 -0600191void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530192{
193 if (isUserExist(userName) == false)
194 {
195 log<level::ERR>("User does not exist",
196 entry("USER_NAME=%s", userName.c_str()));
197 elog<UserNameDoesNotExist>();
198 }
199}
200
Patrick Williams9638afb2021-02-22 17:16:24 -0600201void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530202{
203 if (isUserExist(userName) == true)
204 {
205 log<level::ERR>("User already exists",
206 entry("USER_NAME=%s", userName.c_str()));
207 elog<UserNameExists>();
208 }
209}
210
211void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600212 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530213{
214 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
215 groupNames.end())
216 {
217 if (userName.length() > ipmiMaxUserNameLen)
218 {
219 log<level::ERR>("IPMI user name length limitation",
220 entry("SIZE=%d", userName.length()));
221 elog<UserNameGroupFail>(
222 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
223 "IPMI length"));
224 }
225 }
226 if (userName.length() > systemMaxUserNameLen)
227 {
228 log<level::ERR>("User name length limitation",
229 entry("SIZE=%d", userName.length()));
230 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
231 Argument::ARGUMENT_VALUE("Invalid length"));
232 }
233 if (!std::regex_match(userName.c_str(),
234 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
235 {
236 log<level::ERR>("Invalid user name",
237 entry("USER_NAME=%s", userName.c_str()));
238 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
239 Argument::ARGUMENT_VALUE("Invalid data"));
240 }
241}
242
243void UserMgr::throwForMaxGrpUserCount(
Patrick Williams9638afb2021-02-22 17:16:24 -0600244 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530245{
246 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
247 groupNames.end())
248 {
249 if (getIpmiUsersCount() >= ipmiMaxUsers)
250 {
251 log<level::ERR>("IPMI user limit reached");
252 elog<NoResource>(
253 xyz::openbmc_project::User::Common::NoResource::REASON(
254 "ipmi user count reached"));
255 }
256 }
257 else
258 {
259 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
260 (maxSystemUsers - ipmiMaxUsers))
261 {
262 log<level::ERR>("Non-ipmi User limit reached");
263 elog<NoResource>(
264 xyz::openbmc_project::User::Common::NoResource::REASON(
265 "Non-ipmi user count reached"));
266 }
267 }
268 return;
269}
270
Patrick Williams9638afb2021-02-22 17:16:24 -0600271void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530272{
273 if (!priv.empty() &&
274 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
275 {
276 log<level::ERR>("Invalid privilege");
277 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
278 Argument::ARGUMENT_VALUE(priv.c_str()));
279 }
280}
281
Patrick Williams9638afb2021-02-22 17:16:24 -0600282void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530283{
Patrick Williams9638afb2021-02-22 17:16:24 -0600284 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530285 {
286 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
287 groupsMgr.end())
288 {
289 log<level::ERR>("Invalid Group Name listed");
290 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
291 Argument::ARGUMENT_VALUE(group.c_str()));
292 }
293 }
294}
295
296void UserMgr::createUser(std::string userName,
297 std::vector<std::string> groupNames, std::string priv,
298 bool enabled)
299{
300 throwForInvalidPrivilege(priv);
301 throwForInvalidGroups(groupNames);
302 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500303 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530304 throwForUserExists(userName);
305 throwForUserNameConstraints(userName, groupNames);
306 throwForMaxGrpUserCount(groupNames);
307
308 std::string groups = getCSVFromVector(groupNames);
309 bool sshRequested = removeStringFromCSV(groups, grpSsh);
310
311 // treat privilege as a group - This is to avoid using different file to
312 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530313 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530314 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530315 if (groups.size() != 0)
316 {
317 groups += ",";
318 }
319 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530320 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530321 try
322 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800323 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
324 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530325 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530326 "-m", "-N", "-s",
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530327 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800328 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530329 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600330 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530331 {
332 log<level::ERR>("Unable to create new user");
333 elog<InternalFailure>();
334 }
335
336 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530337 sdbusplus::message::object_path tempObjPath(usersObjPath);
338 tempObjPath /= userName;
339 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530340 std::sort(groupNames.begin(), groupNames.end());
341 usersList.emplace(
342 userName, std::move(std::make_unique<phosphor::user::Users>(
343 bus, userObj.c_str(), groupNames, priv, enabled, *this)));
344
345 log<level::INFO>("User created successfully",
346 entry("USER_NAME=%s", userName.c_str()));
347 return;
348}
349
350void UserMgr::deleteUser(std::string userName)
351{
352 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500353 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530354 throwForUserDoesNotExist(userName);
355 try
356 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530357 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530358 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600359 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530360 {
361 log<level::ERR>("User delete failed",
362 entry("USER_NAME=%s", userName.c_str()));
363 elog<InternalFailure>();
364 }
365
366 usersList.erase(userName);
367
368 log<level::INFO>("User deleted successfully",
369 entry("USER_NAME=%s", userName.c_str()));
370 return;
371}
372
373void UserMgr::renameUser(std::string userName, std::string newUserName)
374{
375 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500376 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530377 throwForUserDoesNotExist(userName);
378 throwForUserExists(newUserName);
379 throwForUserNameConstraints(newUserName,
380 usersList[userName].get()->userGroups());
381 try
382 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530383 std::string newHomeDir = "/home/" + newUserName;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530384 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530385 userName.c_str(), "-d", newHomeDir.c_str(), "-m");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530386 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600387 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530388 {
389 log<level::ERR>("User rename failed",
390 entry("USER_NAME=%s", userName.c_str()));
391 elog<InternalFailure>();
392 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600393 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530394 std::string priv = user.get()->userPrivilege();
395 std::vector<std::string> groupNames = user.get()->userGroups();
396 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530397 sdbusplus::message::object_path tempObjPath(usersObjPath);
398 tempObjPath /= newUserName;
399 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530400 // Special group 'ipmi' needs a way to identify user renamed, in order to
401 // update encrypted password. It can't rely only on InterfacesRemoved &
402 // InterfacesAdded. So first send out userRenamed signal.
403 this->userRenamed(userName, newUserName);
404 usersList.erase(userName);
405 usersList.emplace(
406 newUserName,
407 std::move(std::make_unique<phosphor::user::Users>(
408 bus, newUserObj.c_str(), groupNames, priv, enabled, *this)));
409 return;
410}
411
Patrick Williams9638afb2021-02-22 17:16:24 -0600412void UserMgr::updateGroupsAndPriv(const std::string& userName,
413 const std::vector<std::string>& groupNames,
414 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530415{
416 throwForInvalidPrivilege(priv);
417 throwForInvalidGroups(groupNames);
418 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500419 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530420 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600421 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530422 usersList[userName].get()->userGroups();
423 std::vector<std::string> groupDiff;
424 // Note: already dealing with sorted group lists.
425 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
426 groupNames.begin(), groupNames.end(),
427 std::back_inserter(groupDiff));
428 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
429 groupDiff.end())
430 {
431 throwForUserNameConstraints(userName, groupNames);
432 throwForMaxGrpUserCount(groupNames);
433 }
434
435 std::string groups = getCSVFromVector(groupNames);
436 bool sshRequested = removeStringFromCSV(groups, grpSsh);
437
438 // treat privilege as a group - This is to avoid using different file to
439 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530440 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530441 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530442 if (groups.size() != 0)
443 {
444 groups += ",";
445 }
446 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530447 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530448 try
449 {
450 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
451 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
452 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600453 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530454 {
455 log<level::ERR>("Unable to modify user privilege / groups");
456 elog<InternalFailure>();
457 }
458
459 log<level::INFO>("User groups / privilege updated successfully",
460 entry("USER_NAME=%s", userName.c_str()));
461 return;
462}
463
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530464uint8_t UserMgr::minPasswordLength(uint8_t value)
465{
466 if (value == AccountPolicyIface::minPasswordLength())
467 {
468 return value;
469 }
470 if (value < minPasswdLength)
471 {
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000472 log<level::ERR>(("Attempting to set minPasswordLength to less than " +
473 std::to_string(minPasswdLength))
474 .c_str(),
475 entry("SIZE=%d", value));
476 elog<InvalidArgument>(
477 Argument::ARGUMENT_NAME("minPasswordLength"),
478 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530479 }
480 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
481 std::to_string(value)) != success)
482 {
483 log<level::ERR>("Unable to set minPasswordLength");
484 elog<InternalFailure>();
485 }
486 return AccountPolicyIface::minPasswordLength(value);
487}
488
489uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
490{
491 if (value == AccountPolicyIface::rememberOldPasswordTimes())
492 {
493 return value;
494 }
495 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
496 std::to_string(value)) != success)
497 {
498 log<level::ERR>("Unable to set rememberOldPasswordTimes");
499 elog<InternalFailure>();
500 }
501 return AccountPolicyIface::rememberOldPasswordTimes(value);
502}
503
504uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
505{
506 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
507 {
508 return value;
509 }
510 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
511 std::to_string(value)) != success)
512 {
513 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
514 elog<InternalFailure>();
515 }
516 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
517}
518
519uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
520{
521 if (value == AccountPolicyIface::accountUnlockTimeout())
522 {
523 return value;
524 }
525 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
526 success)
527 {
528 log<level::ERR>("Unable to set accountUnlockTimeout");
529 elog<InternalFailure>();
530 }
531 return AccountPolicyIface::accountUnlockTimeout(value);
532}
533
Patrick Williams9638afb2021-02-22 17:16:24 -0600534int UserMgr::getPamModuleArgValue(const std::string& moduleName,
535 const std::string& argName,
536 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530537{
538 std::string fileName;
539 if (moduleName == pamTally2)
540 {
541 fileName = pamAuthConfigFile;
542 }
543 else
544 {
545 fileName = pamPasswdConfigFile;
546 }
547 std::ifstream fileToRead(fileName, std::ios::in);
548 if (!fileToRead.is_open())
549 {
550 log<level::ERR>("Failed to open pam configuration file",
551 entry("FILE_NAME=%s", fileName.c_str()));
552 return failure;
553 }
554 std::string line;
555 auto argSearch = argName + "=";
556 size_t startPos = 0;
557 size_t endPos = 0;
558 while (getline(fileToRead, line))
559 {
560 // skip comments section starting with #
561 if ((startPos = line.find('#')) != std::string::npos)
562 {
563 if (startPos == 0)
564 {
565 continue;
566 }
567 // skip comments after meaningful section and process those
568 line = line.substr(0, startPos);
569 }
570 if (line.find(moduleName) != std::string::npos)
571 {
572 if ((startPos = line.find(argSearch)) != std::string::npos)
573 {
574 if ((endPos = line.find(' ', startPos)) == std::string::npos)
575 {
576 endPos = line.size();
577 }
578 startPos += argSearch.size();
579 argValue = line.substr(startPos, endPos - startPos);
580 return success;
581 }
582 }
583 }
584 return failure;
585}
586
Patrick Williams9638afb2021-02-22 17:16:24 -0600587int UserMgr::setPamModuleArgValue(const std::string& moduleName,
588 const std::string& argName,
589 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530590{
591 std::string fileName;
592 if (moduleName == pamTally2)
593 {
594 fileName = pamAuthConfigFile;
595 }
596 else
597 {
598 fileName = pamPasswdConfigFile;
599 }
600 std::string tmpFileName = fileName + "_tmp";
601 std::ifstream fileToRead(fileName, std::ios::in);
602 std::ofstream fileToWrite(tmpFileName, std::ios::out);
603 if (!fileToRead.is_open() || !fileToWrite.is_open())
604 {
605 log<level::ERR>("Failed to open pam configuration /tmp file",
606 entry("FILE_NAME=%s", fileName.c_str()));
607 return failure;
608 }
609 std::string line;
610 auto argSearch = argName + "=";
611 size_t startPos = 0;
612 size_t endPos = 0;
613 bool found = false;
614 while (getline(fileToRead, line))
615 {
616 // skip comments section starting with #
617 if ((startPos = line.find('#')) != std::string::npos)
618 {
619 if (startPos == 0)
620 {
621 fileToWrite << line << std::endl;
622 continue;
623 }
624 // skip comments after meaningful section and process those
625 line = line.substr(0, startPos);
626 }
627 if (line.find(moduleName) != std::string::npos)
628 {
629 if ((startPos = line.find(argSearch)) != std::string::npos)
630 {
631 if ((endPos = line.find(' ', startPos)) == std::string::npos)
632 {
633 endPos = line.size();
634 }
635 startPos += argSearch.size();
636 fileToWrite << line.substr(0, startPos) << argValue
637 << line.substr(endPos, line.size() - endPos)
638 << std::endl;
639 found = true;
640 continue;
641 }
642 }
643 fileToWrite << line << std::endl;
644 }
645 fileToWrite.close();
646 fileToRead.close();
647 if (found)
648 {
649 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
650 {
651 return success;
652 }
653 }
654 return failure;
655}
656
Patrick Williams9638afb2021-02-22 17:16:24 -0600657void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530658{
659 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500660 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530661 throwForUserDoesNotExist(userName);
662 try
663 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800664 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
665 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530666 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800667 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530668 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600669 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530670 {
671 log<level::ERR>("Unable to modify user enabled state");
672 elog<InternalFailure>();
673 }
674
675 log<level::INFO>("User enabled/disabled state updated successfully",
676 entry("USER_NAME=%s", userName.c_str()),
677 entry("ENABLED=%d", enabled));
678 return;
679}
680
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530681/**
682 * pam_tally2 app will provide the user failure count and failure status
683 * in second line of output with words position [0] - user name,
684 * [1] - failure count, [2] - latest timestamp, [3] - failure timestamp
685 * [4] - failure app
686 **/
687
688static constexpr size_t t2UserIdx = 0;
689static constexpr size_t t2FailCntIdx = 1;
690static constexpr size_t t2OutputIndex = 1;
691
Patrick Williams9638afb2021-02-22 17:16:24 -0600692bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530693{
694 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500695 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530696 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800697 try
698 {
699 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
700 }
701 catch (const InternalFailure& e)
702 {
703 log<level::ERR>("Unable to read login failure counter");
704 elog<InternalFailure>();
705 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530706
707 std::vector<std::string> splitWords;
708 boost::algorithm::split(splitWords, output[t2OutputIndex],
709 boost::algorithm::is_any_of("\t "),
710 boost::token_compress_on);
711
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530712 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530713 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530714 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
715 uint16_t value16 = 0;
716 if (tmp > std::numeric_limits<decltype(value16)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530717 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530718 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530719 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530720 value16 = static_cast<decltype(value16)>(tmp);
721 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() != 0 &&
722 value16 >= AccountPolicyIface::maxLoginAttemptBeforeLockout())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530723 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530724 return true; // User account is locked out
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530725 }
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530726 return false; // User account is un-locked
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530727 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600728 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530729 {
730 log<level::ERR>("Exception for userLockedForFailedAttempt",
731 entry("WHAT=%s", e.what()));
732 throw;
733 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530734}
735
Patrick Williams9638afb2021-02-22 17:16:24 -0600736bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
737 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530738{
739 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500740 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530741 if (value == true)
742 {
743 return userLockedForFailedAttempt(userName);
744 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530745
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800746 try
747 {
748 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
749 }
750 catch (const InternalFailure& e)
751 {
752 log<level::ERR>("Unable to reset login failure counter");
753 elog<InternalFailure>();
754 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530755
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530756 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530757}
758
Patrick Williams9638afb2021-02-22 17:16:24 -0600759bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600760{
761 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500762 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600763
764 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600765 {};
766 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600767 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
768 if (buflen < -1)
769 {
770 // Use a default size if there is no hard limit suggested by sysconf()
771 buflen = 1024;
772 }
773 std::vector<char> buffer(buflen);
774 auto status =
775 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
776 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
777 // If no matching password record was found, these functions return 0
778 // and store NULL in *spwdPtr
779 if ((status == 0) && (&spwd == spwdPtr))
780 {
781 // Determine password validity per "chage" docs, where:
782 // spwd.sp_lstchg == 0 means password is expired, and
783 // spwd.sp_max == -1 means the password does not expire.
784 constexpr long seconds_per_day = 60 * 60 * 24;
785 long today = static_cast<long>(time(NULL)) / seconds_per_day;
786 if ((spwd.sp_lstchg == 0) ||
787 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
788 {
789 return true;
790 }
791 }
792 else
793 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000794 // User entry is missing in /etc/shadow, indicating no SHA password.
795 // Treat this as new user without password entry in /etc/shadow
796 // TODO: Add property to indicate user password was not set yet
797 // https://github.com/openbmc/phosphor-user-manager/issues/8
798 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600799 }
800
801 return false;
802}
803
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530804UserSSHLists UserMgr::getUserAndSshGrpList()
805{
806 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500807 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530808
809 std::vector<std::string> userList;
810 std::vector<std::string> sshUsersList;
811 struct passwd pw, *pwp = nullptr;
812 std::array<char, 1024> buffer{};
813
814 phosphor::user::File passwd(passwdFileName, "r");
815 if ((passwd)() == NULL)
816 {
817 log<level::ERR>("Error opening the passwd file");
818 elog<InternalFailure>();
819 }
820
821 while (true)
822 {
823 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
824 &pwp);
825 if ((r != 0) || (pwp == NULL))
826 {
827 // Any error, break the loop.
828 break;
829 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530830#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530831 // Add all users whose UID >= 1000 and < 65534
832 // and special UID 0.
833 if ((pwp->pw_uid == 0) ||
834 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530835#else
836 // Add all users whose UID >=1000 and < 65534
837 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
838#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530839 {
840 std::string userName(pwp->pw_name);
841 userList.emplace_back(userName);
842
843 // ssh doesn't have separate group. Check login shell entry to
844 // get all users list which are member of ssh group.
845 std::string loginShell(pwp->pw_shell);
846 if (loginShell == "/bin/sh")
847 {
848 sshUsersList.emplace_back(userName);
849 }
850 }
851 }
852 endpwent();
853 return std::make_pair(std::move(userList), std::move(sshUsersList));
854}
855
856size_t UserMgr::getIpmiUsersCount()
857{
858 std::vector<std::string> userList = getUsersInGroup("ipmi");
859 return userList.size();
860}
861
Patrick Williams9638afb2021-02-22 17:16:24 -0600862bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530863{
864 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500865 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530866 std::array<char, 4096> buffer{};
867 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600868 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530869 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
870 buffer.max_size(), &resultPtr);
871 if (!status && (&spwd == resultPtr))
872 {
873 if (resultPtr->sp_expire >= 0)
874 {
875 return false; // user locked out
876 }
877 return true;
878 }
879 return false; // assume user is disabled for any error.
880}
881
Patrick Williams9638afb2021-02-22 17:16:24 -0600882std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530883{
884 std::vector<std::string> usersInGroup;
885 // Should be more than enough to get the pwd structure.
886 std::array<char, 4096> buffer{};
887 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600888 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530889
890 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
891 buffer.max_size(), &resultPtr);
892
893 if (!status && (&grp == resultPtr))
894 {
895 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
896 {
897 usersInGroup.emplace_back(*(grp.gr_mem));
898 }
899 }
900 else
901 {
902 log<level::ERR>("Group not found",
903 entry("GROUP=%s", groupName.c_str()));
904 // Don't throw error, just return empty userList - fallback
905 }
906 return usersInGroup;
907}
908
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600909DbusUserObj UserMgr::getPrivilegeMapperObject(void)
910{
911 DbusUserObj objects;
912 try
913 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500914 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
915 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600916
917 auto ldapMgmtService =
918 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600919 auto method = bus.new_method_call(
920 ldapMgmtService.c_str(), ldapMgrObjBasePath,
921 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
922
923 auto reply = bus.call(method);
924 reply.read(objects);
925 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600926 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600927 {
928 log<level::ERR>("Unable to get the User Service",
929 entry("WHAT=%s", e.what()));
930 throw;
931 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -0500932 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600933 {
934 log<level::ERR>(
935 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
936 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
937 throw;
938 }
939 return objects;
940}
941
Patrick Williams9638afb2021-02-22 17:16:24 -0600942std::string UserMgr::getLdapGroupName(const std::string& userName)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600943{
944 struct passwd pwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600945 {};
946 struct passwd* pwdPtr = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600947 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
948 if (buflen < -1)
949 {
950 // Use a default size if there is no hard limit suggested by sysconf()
951 buflen = 1024;
952 }
953 std::vector<char> buffer(buflen);
954 gid_t gid = 0;
955
956 auto status =
957 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
958 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
959 // If no matching password record was found, these functions return 0
960 // and store NULL in *pwdPtr
961 if (!status && (&pwd == pwdPtr))
962 {
963 gid = pwd.pw_gid;
964 }
965 else
966 {
967 log<level::ERR>("User does not exist",
968 entry("USER_NAME=%s", userName.c_str()));
969 elog<UserNameDoesNotExist>();
970 }
971
Patrick Williams9638afb2021-02-22 17:16:24 -0600972 struct group* groups = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600973 std::string ldapGroupName;
974
975 while ((groups = getgrent()) != NULL)
976 {
977 if (groups->gr_gid == gid)
978 {
979 ldapGroupName = groups->gr_name;
980 break;
981 }
982 }
983 // Call endgrent() to close the group database.
984 endgrent();
985
986 return ldapGroupName;
987}
988
Patrick Williams9638afb2021-02-22 17:16:24 -0600989std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600990{
991 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
992 objMapperInterface, "GetObject");
993
994 mapperCall.append(std::move(path));
995 mapperCall.append(std::vector<std::string>({std::move(intf)}));
996
997 auto mapperResponseMsg = bus.call(mapperCall);
998
999 if (mapperResponseMsg.is_method_error())
1000 {
1001 log<level::ERR>("Error in mapper call");
1002 elog<InternalFailure>();
1003 }
1004
1005 std::map<std::string, std::vector<std::string>> mapperResponse;
1006 mapperResponseMsg.read(mapperResponse);
1007
1008 if (mapperResponse.begin() == mapperResponse.end())
1009 {
1010 log<level::ERR>("Invalid response from mapper");
1011 elog<InternalFailure>();
1012 }
1013
1014 return mapperResponse.begin()->first;
1015}
1016
1017UserInfoMap UserMgr::getUserInfo(std::string userName)
1018{
1019 UserInfoMap userInfo;
1020 // Check whether the given user is local user or not.
1021 if (isUserExist(userName) == true)
1022 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001023 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001024 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1025 userInfo.emplace("UserGroups", user.get()->userGroups());
1026 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1027 userInfo.emplace("UserLockedForFailedAttempt",
1028 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001029 userInfo.emplace("UserPasswordExpired",
1030 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001031 userInfo.emplace("RemoteUser", false);
1032 }
1033 else
1034 {
1035 std::string ldapGroupName = getLdapGroupName(userName);
1036 if (ldapGroupName.empty())
1037 {
1038 log<level::ERR>("Unable to get group name",
1039 entry("USER_NAME=%s", userName.c_str()));
1040 elog<InternalFailure>();
1041 }
1042
1043 DbusUserObj objects = getPrivilegeMapperObject();
1044
1045 std::string privilege;
1046 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001047 std::string ldapConfigPath;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001048
1049 try
1050 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001051 for (const auto& obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001052 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001053 for (const auto& interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001054 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001055 if ((interface.first ==
1056 "xyz.openbmc_project.Object.Enable"))
1057 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001058 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001059 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001060 auto value = std::get<bool>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001061 if ((property.first == "Enabled") &&
1062 (value == true))
1063 {
1064 ldapConfigPath = obj.first;
1065 break;
1066 }
1067 }
1068 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001069 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001070 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001071 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001072 break;
1073 }
1074 }
1075
1076 if (ldapConfigPath.empty())
1077 {
1078 return userInfo;
1079 }
1080
Patrick Williams9638afb2021-02-22 17:16:24 -06001081 for (const auto& obj : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001082 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001083 for (const auto& interface : obj.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001084 {
1085 if ((interface.first ==
1086 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1087 (obj.first.str.find(ldapConfigPath) !=
1088 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001089 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001090
Patrick Williams9638afb2021-02-22 17:16:24 -06001091 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001092 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001093 auto value = std::get<std::string>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001094 if (property.first == "GroupName")
1095 {
1096 groupName = value;
1097 }
1098 else if (property.first == "Privilege")
1099 {
1100 privilege = value;
1101 }
1102 if (groupName == ldapGroupName)
1103 {
1104 userInfo["UserPrivilege"] = privilege;
1105 }
1106 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001107 }
1108 }
1109 }
1110 auto priv = std::get<std::string>(userInfo["UserPrivilege"]);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001111
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001112 if (priv.empty())
1113 {
1114 log<level::ERR>("LDAP group privilege mapping does not exist");
1115 }
1116 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001117 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001118 {
1119 log<level::ERR>("Error while accessing variant",
1120 entry("WHAT=%s", e.what()));
1121 elog<InternalFailure>();
1122 }
1123 userInfo.emplace("RemoteUser", true);
1124 }
1125
1126 return userInfo;
1127}
1128
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301129void UserMgr::initUserObjects(void)
1130{
1131 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001132 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301133 std::vector<std::string> userNameList;
1134 std::vector<std::string> sshGrpUsersList;
1135 UserSSHLists userSSHLists = getUserAndSshGrpList();
1136 userNameList = std::move(userSSHLists.first);
1137 sshGrpUsersList = std::move(userSSHLists.second);
1138
1139 if (!userNameList.empty())
1140 {
1141 std::map<std::string, std::vector<std::string>> groupLists;
Patrick Williams9638afb2021-02-22 17:16:24 -06001142 for (auto& grp : groupsMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301143 {
1144 if (grp == grpSsh)
1145 {
1146 groupLists.emplace(grp, sshGrpUsersList);
1147 }
1148 else
1149 {
1150 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1151 groupLists.emplace(grp, grpUsersList);
1152 }
1153 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001154 for (auto& grp : privMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301155 {
1156 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1157 groupLists.emplace(grp, grpUsersList);
1158 }
1159
Patrick Williams9638afb2021-02-22 17:16:24 -06001160 for (auto& user : userNameList)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301161 {
1162 std::vector<std::string> userGroups;
1163 std::string userPriv;
Patrick Williams9638afb2021-02-22 17:16:24 -06001164 for (const auto& grp : groupLists)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301165 {
1166 std::vector<std::string> tempGrp = grp.second;
1167 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1168 tempGrp.end())
1169 {
1170 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1171 privMgr.end())
1172 {
1173 userPriv = grp.first;
1174 }
1175 else
1176 {
1177 userGroups.emplace_back(grp.first);
1178 }
1179 }
1180 }
1181 // Add user objects to the Users path.
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +05301182 sdbusplus::message::object_path tempObjPath(usersObjPath);
1183 tempObjPath /= user;
1184 std::string objPath(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301185 std::sort(userGroups.begin(), userGroups.end());
1186 usersList.emplace(user,
1187 std::move(std::make_unique<phosphor::user::Users>(
1188 bus, objPath.c_str(), userGroups, userPriv,
1189 isUserEnabled(user), *this)));
1190 }
1191 }
1192}
1193
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001194UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
Patrick Williams224559b2022-04-05 16:10:39 -05001195 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301196{
1197 UserMgrIface::allPrivileges(privMgr);
1198 std::sort(groupsMgr.begin(), groupsMgr.end());
1199 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301200 std::string valueStr;
1201 auto value = minPasswdLength;
1202 unsigned long tmp = 0;
1203 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1204 success)
1205 {
1206 AccountPolicyIface::minPasswordLength(minPasswdLength);
1207 }
1208 else
1209 {
1210 try
1211 {
1212 tmp = std::stoul(valueStr, nullptr);
1213 if (tmp > std::numeric_limits<decltype(value)>::max())
1214 {
1215 throw std::out_of_range("Out of range");
1216 }
1217 value = static_cast<decltype(value)>(tmp);
1218 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001219 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301220 {
1221 log<level::ERR>("Exception for MinPasswordLength",
1222 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001223 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301224 }
1225 AccountPolicyIface::minPasswordLength(value);
1226 }
1227 valueStr.clear();
1228 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1229 success)
1230 {
1231 AccountPolicyIface::rememberOldPasswordTimes(0);
1232 }
1233 else
1234 {
1235 value = 0;
1236 try
1237 {
1238 tmp = std::stoul(valueStr, nullptr);
1239 if (tmp > std::numeric_limits<decltype(value)>::max())
1240 {
1241 throw std::out_of_range("Out of range");
1242 }
1243 value = static_cast<decltype(value)>(tmp);
1244 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001245 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301246 {
1247 log<level::ERR>("Exception for RememberOldPasswordTimes",
1248 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001249 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301250 }
1251 AccountPolicyIface::rememberOldPasswordTimes(value);
1252 }
1253 valueStr.clear();
1254 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1255 {
1256 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1257 }
1258 else
1259 {
1260 uint16_t value16 = 0;
1261 try
1262 {
1263 tmp = std::stoul(valueStr, nullptr);
1264 if (tmp > std::numeric_limits<decltype(value16)>::max())
1265 {
1266 throw std::out_of_range("Out of range");
1267 }
1268 value16 = static_cast<decltype(value16)>(tmp);
1269 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001270 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301271 {
1272 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1273 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001274 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301275 }
1276 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1277 }
1278 valueStr.clear();
1279 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1280 {
1281 AccountPolicyIface::accountUnlockTimeout(0);
1282 }
1283 else
1284 {
1285 uint32_t value32 = 0;
1286 try
1287 {
1288 tmp = std::stoul(valueStr, nullptr);
1289 if (tmp > std::numeric_limits<decltype(value32)>::max())
1290 {
1291 throw std::out_of_range("Out of range");
1292 }
1293 value32 = static_cast<decltype(value32)>(tmp);
1294 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001295 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301296 {
1297 log<level::ERR>("Exception for AccountUnlockTimeout",
1298 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001299 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301300 }
1301 AccountPolicyIface::accountUnlockTimeout(value32);
1302 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301303 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301304
1305 // emit the signal
1306 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301307}
1308
1309} // namespace user
1310} // namespace phosphor