blob: b2047b26ec51038b2fbbd34a51889abf065224e5 [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>
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +080043#include <ctime>
Patrick Williams9638afb2021-02-22 17:16:24 -060044#include <fstream>
45#include <numeric>
46#include <regex>
Nan Zhoue47c09d2022-10-25 00:06:41 +000047#include <span>
48#include <string>
49#include <string_view>
50#include <vector>
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053051
52namespace phosphor
53{
54namespace user
55{
56
Patrick Williams9638afb2021-02-22 17:16:24 -060057static constexpr const char* passwdFileName = "/etc/passwd";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053058static constexpr size_t ipmiMaxUsers = 15;
59static constexpr size_t ipmiMaxUserNameLen = 16;
60static constexpr size_t systemMaxUserNameLen = 30;
61static constexpr size_t maxSystemUsers = 30;
Patrick Williams9638afb2021-02-22 17:16:24 -060062static constexpr const char* grpSsh = "ssh";
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +053063static constexpr uint8_t minPasswdLength = 8;
64static constexpr int success = 0;
65static constexpr int failure = -1;
66
67// pam modules related
Patrick Williams9638afb2021-02-22 17:16:24 -060068static constexpr const char* pamTally2 = "pam_tally2.so";
69static constexpr const char* pamCrackLib = "pam_cracklib.so";
70static constexpr const char* pamPWHistory = "pam_pwhistory.so";
71static constexpr const char* minPasswdLenProp = "minlen";
72static constexpr const char* remOldPasswdCount = "remember";
73static constexpr const char* maxFailedAttempt = "deny";
74static constexpr const char* unlockTimeout = "unlock_time";
75static constexpr const char* pamPasswdConfigFile = "/etc/pam.d/common-password";
76static constexpr const char* pamAuthConfigFile = "/etc/pam.d/common-auth";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053077
Ratan Guptaaeaf9412019-02-11 04:41:52 -060078// Object Manager related
Patrick Williams9638afb2021-02-22 17:16:24 -060079static constexpr const char* ldapMgrObjBasePath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060080 "/xyz/openbmc_project/user/ldap";
81
82// Object Mapper related
Patrick Williams9638afb2021-02-22 17:16:24 -060083static constexpr const char* objMapperService =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060084 "xyz.openbmc_project.ObjectMapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060085static constexpr const char* objMapperPath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060086 "/xyz/openbmc_project/object_mapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060087static constexpr const char* objMapperInterface =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060088 "xyz.openbmc_project.ObjectMapper";
89
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053090using namespace phosphor::logging;
91using InsufficientPermission =
92 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
93using InternalFailure =
94 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
95using InvalidArgument =
96 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
97using UserNameExists =
98 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
99using UserNameDoesNotExist =
100 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
101using UserNameGroupFail =
102 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530103using NoResource =
104 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
105
106using Argument = xyz::openbmc_project::Common::InvalidArgument;
107
108template <typename... ArgTypes>
Patrick Williams9638afb2021-02-22 17:16:24 -0600109static std::vector<std::string> executeCmd(const char* path,
110 ArgTypes&&... tArgs)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530111{
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530112 std::vector<std::string> stdOutput;
113 boost::process::ipstream stdOutStream;
Patrick Williams9638afb2021-02-22 17:16:24 -0600114 boost::process::child execProg(path, const_cast<char*>(tArgs)...,
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530115 boost::process::std_out > stdOutStream);
116 std::string stdOutLine;
117
118 while (stdOutStream && std::getline(stdOutStream, stdOutLine) &&
119 !stdOutLine.empty())
120 {
121 stdOutput.emplace_back(stdOutLine);
122 }
123
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530124 execProg.wait();
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530125
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530126 int retCode = execProg.exit_code();
127 if (retCode)
128 {
Jonathan Domanccd28892021-10-14 16:43:33 -0700129 log<level::ERR>("Command execution failed", entry("PATH=%s", path),
130 entry("RETURN_CODE=%d", retCode));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530131 elog<InternalFailure>();
132 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530133
134 return stdOutput;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530135}
136
Nan Zhoue47c09d2022-10-25 00:06:41 +0000137std::string getCSVFromVector(std::span<const std::string> vec)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530138{
Nan Zhoue47c09d2022-10-25 00:06:41 +0000139 if (vec.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530140 {
Nan Zhoue47c09d2022-10-25 00:06:41 +0000141 return "";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530142 }
Nan Zhoue47c09d2022-10-25 00:06:41 +0000143 return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
144 [](std::string&& val, std::string_view element) {
145 val += ',';
146 val += element;
147 return val;
148 });
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530149}
150
Nan Zhou332fb9d2022-10-25 00:07:03 +0000151bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530152{
153 std::string::size_type delStrPos = csvStr.find(delStr);
154 if (delStrPos != std::string::npos)
155 {
156 // need to also delete the comma char
157 if (delStrPos == 0)
158 {
159 csvStr.erase(delStrPos, delStr.size() + 1);
160 }
161 else
162 {
163 csvStr.erase(delStrPos - 1, delStr.size() + 1);
164 }
165 return true;
166 }
167 return false;
168}
169
Patrick Williams9638afb2021-02-22 17:16:24 -0600170bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530171{
172 if (userName.empty())
173 {
174 log<level::ERR>("User name is empty");
175 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
176 Argument::ARGUMENT_VALUE("Null"));
177 }
178 if (usersList.find(userName) == usersList.end())
179 {
180 return false;
181 }
182 return true;
183}
184
Patrick Williams9638afb2021-02-22 17:16:24 -0600185void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530186{
187 if (isUserExist(userName) == false)
188 {
189 log<level::ERR>("User does not exist",
190 entry("USER_NAME=%s", userName.c_str()));
191 elog<UserNameDoesNotExist>();
192 }
193}
194
Patrick Williams9638afb2021-02-22 17:16:24 -0600195void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530196{
197 if (isUserExist(userName) == true)
198 {
199 log<level::ERR>("User already exists",
200 entry("USER_NAME=%s", userName.c_str()));
201 elog<UserNameExists>();
202 }
203}
204
205void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600206 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530207{
208 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
209 groupNames.end())
210 {
211 if (userName.length() > ipmiMaxUserNameLen)
212 {
213 log<level::ERR>("IPMI user name length limitation",
214 entry("SIZE=%d", userName.length()));
215 elog<UserNameGroupFail>(
216 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
217 "IPMI length"));
218 }
219 }
220 if (userName.length() > systemMaxUserNameLen)
221 {
222 log<level::ERR>("User name length limitation",
223 entry("SIZE=%d", userName.length()));
224 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
225 Argument::ARGUMENT_VALUE("Invalid length"));
226 }
227 if (!std::regex_match(userName.c_str(),
228 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
229 {
230 log<level::ERR>("Invalid user name",
231 entry("USER_NAME=%s", userName.c_str()));
232 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
233 Argument::ARGUMENT_VALUE("Invalid data"));
234 }
235}
236
237void UserMgr::throwForMaxGrpUserCount(
Patrick Williams9638afb2021-02-22 17:16:24 -0600238 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530239{
240 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
241 groupNames.end())
242 {
243 if (getIpmiUsersCount() >= ipmiMaxUsers)
244 {
245 log<level::ERR>("IPMI user limit reached");
246 elog<NoResource>(
247 xyz::openbmc_project::User::Common::NoResource::REASON(
248 "ipmi user count reached"));
249 }
250 }
251 else
252 {
253 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
254 (maxSystemUsers - ipmiMaxUsers))
255 {
256 log<level::ERR>("Non-ipmi User limit reached");
257 elog<NoResource>(
258 xyz::openbmc_project::User::Common::NoResource::REASON(
259 "Non-ipmi user count reached"));
260 }
261 }
262 return;
263}
264
Patrick Williams9638afb2021-02-22 17:16:24 -0600265void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530266{
267 if (!priv.empty() &&
268 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
269 {
270 log<level::ERR>("Invalid privilege");
271 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
272 Argument::ARGUMENT_VALUE(priv.c_str()));
273 }
274}
275
Patrick Williams9638afb2021-02-22 17:16:24 -0600276void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530277{
Patrick Williams9638afb2021-02-22 17:16:24 -0600278 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530279 {
280 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
281 groupsMgr.end())
282 {
283 log<level::ERR>("Invalid Group Name listed");
284 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
285 Argument::ARGUMENT_VALUE(group.c_str()));
286 }
287 }
288}
289
290void UserMgr::createUser(std::string userName,
291 std::vector<std::string> groupNames, std::string priv,
292 bool enabled)
293{
294 throwForInvalidPrivilege(priv);
295 throwForInvalidGroups(groupNames);
296 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500297 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530298 throwForUserExists(userName);
299 throwForUserNameConstraints(userName, groupNames);
300 throwForMaxGrpUserCount(groupNames);
301
302 std::string groups = getCSVFromVector(groupNames);
303 bool sshRequested = removeStringFromCSV(groups, grpSsh);
304
305 // treat privilege as a group - This is to avoid using different file to
306 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530307 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530308 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530309 if (groups.size() != 0)
310 {
311 groups += ",";
312 }
313 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530314 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530315 try
316 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800317 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
318 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530319 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530320 "-m", "-N", "-s",
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530321 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800322 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530323 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600324 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530325 {
326 log<level::ERR>("Unable to create new user");
327 elog<InternalFailure>();
328 }
329
330 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530331 sdbusplus::message::object_path tempObjPath(usersObjPath);
332 tempObjPath /= userName;
333 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530334 std::sort(groupNames.begin(), groupNames.end());
335 usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +0000336 userName, std::make_unique<phosphor::user::Users>(
337 bus, userObj.c_str(), groupNames, priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530338
339 log<level::INFO>("User created successfully",
340 entry("USER_NAME=%s", userName.c_str()));
341 return;
342}
343
344void UserMgr::deleteUser(std::string userName)
345{
346 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500347 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530348 throwForUserDoesNotExist(userName);
349 try
350 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530351 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530352 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600353 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530354 {
355 log<level::ERR>("User delete failed",
356 entry("USER_NAME=%s", userName.c_str()));
357 elog<InternalFailure>();
358 }
359
360 usersList.erase(userName);
361
362 log<level::INFO>("User deleted successfully",
363 entry("USER_NAME=%s", userName.c_str()));
364 return;
365}
366
367void UserMgr::renameUser(std::string userName, std::string newUserName)
368{
369 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500370 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530371 throwForUserDoesNotExist(userName);
372 throwForUserExists(newUserName);
373 throwForUserNameConstraints(newUserName,
374 usersList[userName].get()->userGroups());
375 try
376 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530377 std::string newHomeDir = "/home/" + newUserName;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530378 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530379 userName.c_str(), "-d", newHomeDir.c_str(), "-m");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530380 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600381 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530382 {
383 log<level::ERR>("User rename failed",
384 entry("USER_NAME=%s", userName.c_str()));
385 elog<InternalFailure>();
386 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600387 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530388 std::string priv = user.get()->userPrivilege();
389 std::vector<std::string> groupNames = user.get()->userGroups();
390 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530391 sdbusplus::message::object_path tempObjPath(usersObjPath);
392 tempObjPath /= newUserName;
393 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530394 // Special group 'ipmi' needs a way to identify user renamed, in order to
395 // update encrypted password. It can't rely only on InterfacesRemoved &
396 // InterfacesAdded. So first send out userRenamed signal.
397 this->userRenamed(userName, newUserName);
398 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000399 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
400 bus, newUserObj.c_str(), groupNames,
401 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530402 return;
403}
404
Patrick Williams9638afb2021-02-22 17:16:24 -0600405void UserMgr::updateGroupsAndPriv(const std::string& userName,
406 const std::vector<std::string>& groupNames,
407 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530408{
409 throwForInvalidPrivilege(priv);
410 throwForInvalidGroups(groupNames);
411 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500412 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530413 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600414 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530415 usersList[userName].get()->userGroups();
416 std::vector<std::string> groupDiff;
417 // Note: already dealing with sorted group lists.
418 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
419 groupNames.begin(), groupNames.end(),
420 std::back_inserter(groupDiff));
421 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
422 groupDiff.end())
423 {
424 throwForUserNameConstraints(userName, groupNames);
425 throwForMaxGrpUserCount(groupNames);
426 }
427
428 std::string groups = getCSVFromVector(groupNames);
429 bool sshRequested = removeStringFromCSV(groups, grpSsh);
430
431 // treat privilege as a group - This is to avoid using different file to
432 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530433 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530434 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530435 if (groups.size() != 0)
436 {
437 groups += ",";
438 }
439 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530440 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530441 try
442 {
443 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
444 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
445 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600446 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530447 {
448 log<level::ERR>("Unable to modify user privilege / groups");
449 elog<InternalFailure>();
450 }
451
452 log<level::INFO>("User groups / privilege updated successfully",
453 entry("USER_NAME=%s", userName.c_str()));
454 return;
455}
456
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530457uint8_t UserMgr::minPasswordLength(uint8_t value)
458{
459 if (value == AccountPolicyIface::minPasswordLength())
460 {
461 return value;
462 }
463 if (value < minPasswdLength)
464 {
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000465 log<level::ERR>(("Attempting to set minPasswordLength to less than " +
466 std::to_string(minPasswdLength))
467 .c_str(),
468 entry("SIZE=%d", value));
469 elog<InvalidArgument>(
470 Argument::ARGUMENT_NAME("minPasswordLength"),
471 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530472 }
473 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
474 std::to_string(value)) != success)
475 {
476 log<level::ERR>("Unable to set minPasswordLength");
477 elog<InternalFailure>();
478 }
479 return AccountPolicyIface::minPasswordLength(value);
480}
481
482uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
483{
484 if (value == AccountPolicyIface::rememberOldPasswordTimes())
485 {
486 return value;
487 }
488 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
489 std::to_string(value)) != success)
490 {
491 log<level::ERR>("Unable to set rememberOldPasswordTimes");
492 elog<InternalFailure>();
493 }
494 return AccountPolicyIface::rememberOldPasswordTimes(value);
495}
496
497uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
498{
499 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
500 {
501 return value;
502 }
503 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
504 std::to_string(value)) != success)
505 {
506 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
507 elog<InternalFailure>();
508 }
509 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
510}
511
512uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
513{
514 if (value == AccountPolicyIface::accountUnlockTimeout())
515 {
516 return value;
517 }
518 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
519 success)
520 {
521 log<level::ERR>("Unable to set accountUnlockTimeout");
522 elog<InternalFailure>();
523 }
524 return AccountPolicyIface::accountUnlockTimeout(value);
525}
526
Patrick Williams9638afb2021-02-22 17:16:24 -0600527int UserMgr::getPamModuleArgValue(const std::string& moduleName,
528 const std::string& argName,
529 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530530{
531 std::string fileName;
532 if (moduleName == pamTally2)
533 {
534 fileName = pamAuthConfigFile;
535 }
536 else
537 {
538 fileName = pamPasswdConfigFile;
539 }
540 std::ifstream fileToRead(fileName, std::ios::in);
541 if (!fileToRead.is_open())
542 {
543 log<level::ERR>("Failed to open pam configuration file",
544 entry("FILE_NAME=%s", fileName.c_str()));
545 return failure;
546 }
547 std::string line;
548 auto argSearch = argName + "=";
549 size_t startPos = 0;
550 size_t endPos = 0;
551 while (getline(fileToRead, line))
552 {
553 // skip comments section starting with #
554 if ((startPos = line.find('#')) != std::string::npos)
555 {
556 if (startPos == 0)
557 {
558 continue;
559 }
560 // skip comments after meaningful section and process those
561 line = line.substr(0, startPos);
562 }
563 if (line.find(moduleName) != std::string::npos)
564 {
565 if ((startPos = line.find(argSearch)) != std::string::npos)
566 {
567 if ((endPos = line.find(' ', startPos)) == std::string::npos)
568 {
569 endPos = line.size();
570 }
571 startPos += argSearch.size();
572 argValue = line.substr(startPos, endPos - startPos);
573 return success;
574 }
575 }
576 }
577 return failure;
578}
579
Patrick Williams9638afb2021-02-22 17:16:24 -0600580int UserMgr::setPamModuleArgValue(const std::string& moduleName,
581 const std::string& argName,
582 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530583{
584 std::string fileName;
585 if (moduleName == pamTally2)
586 {
587 fileName = pamAuthConfigFile;
588 }
589 else
590 {
591 fileName = pamPasswdConfigFile;
592 }
593 std::string tmpFileName = fileName + "_tmp";
594 std::ifstream fileToRead(fileName, std::ios::in);
595 std::ofstream fileToWrite(tmpFileName, std::ios::out);
596 if (!fileToRead.is_open() || !fileToWrite.is_open())
597 {
598 log<level::ERR>("Failed to open pam configuration /tmp file",
599 entry("FILE_NAME=%s", fileName.c_str()));
600 return failure;
601 }
602 std::string line;
603 auto argSearch = argName + "=";
604 size_t startPos = 0;
605 size_t endPos = 0;
606 bool found = false;
607 while (getline(fileToRead, line))
608 {
609 // skip comments section starting with #
610 if ((startPos = line.find('#')) != std::string::npos)
611 {
612 if (startPos == 0)
613 {
614 fileToWrite << line << std::endl;
615 continue;
616 }
617 // skip comments after meaningful section and process those
618 line = line.substr(0, startPos);
619 }
620 if (line.find(moduleName) != std::string::npos)
621 {
622 if ((startPos = line.find(argSearch)) != std::string::npos)
623 {
624 if ((endPos = line.find(' ', startPos)) == std::string::npos)
625 {
626 endPos = line.size();
627 }
628 startPos += argSearch.size();
629 fileToWrite << line.substr(0, startPos) << argValue
630 << line.substr(endPos, line.size() - endPos)
631 << std::endl;
632 found = true;
633 continue;
634 }
635 }
636 fileToWrite << line << std::endl;
637 }
638 fileToWrite.close();
639 fileToRead.close();
640 if (found)
641 {
642 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
643 {
644 return success;
645 }
646 }
647 return failure;
648}
649
Patrick Williams9638afb2021-02-22 17:16:24 -0600650void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530651{
652 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500653 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530654 throwForUserDoesNotExist(userName);
655 try
656 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800657 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
658 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530659 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800660 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530661 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600662 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530663 {
664 log<level::ERR>("Unable to modify user enabled state");
665 elog<InternalFailure>();
666 }
667
668 log<level::INFO>("User enabled/disabled state updated successfully",
669 entry("USER_NAME=%s", userName.c_str()),
670 entry("ENABLED=%d", enabled));
671 return;
672}
673
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530674/**
675 * pam_tally2 app will provide the user failure count and failure status
676 * in second line of output with words position [0] - user name,
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800677 * [1] - failure count, [2] - latest failure date, [3] - latest failure time
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530678 * [4] - failure app
679 **/
680
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530681static constexpr size_t t2FailCntIdx = 1;
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800682static constexpr size_t t2FailDateIdx = 2;
683static constexpr size_t t2FailTimeIdx = 3;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530684static constexpr size_t t2OutputIndex = 1;
685
Patrick Williams9638afb2021-02-22 17:16:24 -0600686bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530687{
688 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500689 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800690 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
691 {
692 return false;
693 }
694
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530695 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800696 try
697 {
698 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
699 }
700 catch (const InternalFailure& e)
701 {
702 log<level::ERR>("Unable to read login failure counter");
703 elog<InternalFailure>();
704 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530705
706 std::vector<std::string> splitWords;
707 boost::algorithm::split(splitWords, output[t2OutputIndex],
708 boost::algorithm::is_any_of("\t "),
709 boost::token_compress_on);
710
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800711 uint16_t failAttempts = 0;
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);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800715 if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530716 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530717 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530718 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800719 failAttempts = static_cast<decltype(failAttempts)>(tmp);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530720 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600721 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530722 {
723 log<level::ERR>("Exception for userLockedForFailedAttempt",
724 entry("WHAT=%s", e.what()));
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800725 elog<InternalFailure>();
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530726 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800727
728 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
729 {
730 return false;
731 }
732
733 // When failedAttempts is not 0, Latest failure date/time should be
734 // available
735 if (splitWords.size() < 4)
736 {
737 log<level::ERR>("Unable to read latest failure date/time");
738 elog<InternalFailure>();
739 }
740
741 const std::string failDateTime =
742 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
743
744 // NOTE: Cannot use std::get_time() here as the implementation of %y in
745 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
746 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
747 std::tm tmStruct = {};
748 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
749 {
750 log<level::ERR>("Failed to parse latest failure date/time");
751 elog<InternalFailure>();
752 }
753
754 time_t failTimestamp = std::mktime(&tmStruct);
755 if (failTimestamp +
756 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
757 std::time(NULL))
758 {
759 return false;
760 }
761
762 return true;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530763}
764
Patrick Williams9638afb2021-02-22 17:16:24 -0600765bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
766 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530767{
768 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500769 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530770 if (value == true)
771 {
772 return userLockedForFailedAttempt(userName);
773 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530774
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800775 try
776 {
777 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
778 }
779 catch (const InternalFailure& e)
780 {
781 log<level::ERR>("Unable to reset login failure counter");
782 elog<InternalFailure>();
783 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530784
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530785 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530786}
787
Patrick Williams9638afb2021-02-22 17:16:24 -0600788bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600789{
790 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500791 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600792
793 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600794 {};
795 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600796 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
797 if (buflen < -1)
798 {
799 // Use a default size if there is no hard limit suggested by sysconf()
800 buflen = 1024;
801 }
802 std::vector<char> buffer(buflen);
803 auto status =
804 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
805 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
806 // If no matching password record was found, these functions return 0
807 // and store NULL in *spwdPtr
808 if ((status == 0) && (&spwd == spwdPtr))
809 {
810 // Determine password validity per "chage" docs, where:
811 // spwd.sp_lstchg == 0 means password is expired, and
812 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000813 constexpr long secondsPerDay = 60 * 60 * 24;
814 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600815 if ((spwd.sp_lstchg == 0) ||
816 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
817 {
818 return true;
819 }
820 }
821 else
822 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000823 // User entry is missing in /etc/shadow, indicating no SHA password.
824 // Treat this as new user without password entry in /etc/shadow
825 // TODO: Add property to indicate user password was not set yet
826 // https://github.com/openbmc/phosphor-user-manager/issues/8
827 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600828 }
829
830 return false;
831}
832
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530833UserSSHLists UserMgr::getUserAndSshGrpList()
834{
835 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500836 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530837
838 std::vector<std::string> userList;
839 std::vector<std::string> sshUsersList;
840 struct passwd pw, *pwp = nullptr;
841 std::array<char, 1024> buffer{};
842
843 phosphor::user::File passwd(passwdFileName, "r");
844 if ((passwd)() == NULL)
845 {
846 log<level::ERR>("Error opening the passwd file");
847 elog<InternalFailure>();
848 }
849
850 while (true)
851 {
852 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
853 &pwp);
854 if ((r != 0) || (pwp == NULL))
855 {
856 // Any error, break the loop.
857 break;
858 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530859#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530860 // Add all users whose UID >= 1000 and < 65534
861 // and special UID 0.
862 if ((pwp->pw_uid == 0) ||
863 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530864#else
865 // Add all users whose UID >=1000 and < 65534
866 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
867#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530868 {
869 std::string userName(pwp->pw_name);
870 userList.emplace_back(userName);
871
872 // ssh doesn't have separate group. Check login shell entry to
873 // get all users list which are member of ssh group.
874 std::string loginShell(pwp->pw_shell);
875 if (loginShell == "/bin/sh")
876 {
877 sshUsersList.emplace_back(userName);
878 }
879 }
880 }
881 endpwent();
882 return std::make_pair(std::move(userList), std::move(sshUsersList));
883}
884
885size_t UserMgr::getIpmiUsersCount()
886{
887 std::vector<std::string> userList = getUsersInGroup("ipmi");
888 return userList.size();
889}
890
Patrick Williams9638afb2021-02-22 17:16:24 -0600891bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530892{
893 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500894 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530895 std::array<char, 4096> buffer{};
896 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600897 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530898 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
899 buffer.max_size(), &resultPtr);
900 if (!status && (&spwd == resultPtr))
901 {
902 if (resultPtr->sp_expire >= 0)
903 {
904 return false; // user locked out
905 }
906 return true;
907 }
908 return false; // assume user is disabled for any error.
909}
910
Patrick Williams9638afb2021-02-22 17:16:24 -0600911std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530912{
913 std::vector<std::string> usersInGroup;
914 // Should be more than enough to get the pwd structure.
915 std::array<char, 4096> buffer{};
916 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600917 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530918
919 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
920 buffer.max_size(), &resultPtr);
921
922 if (!status && (&grp == resultPtr))
923 {
924 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
925 {
926 usersInGroup.emplace_back(*(grp.gr_mem));
927 }
928 }
929 else
930 {
931 log<level::ERR>("Group not found",
932 entry("GROUP=%s", groupName.c_str()));
933 // Don't throw error, just return empty userList - fallback
934 }
935 return usersInGroup;
936}
937
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600938DbusUserObj UserMgr::getPrivilegeMapperObject(void)
939{
940 DbusUserObj objects;
941 try
942 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500943 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
944 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600945
946 auto ldapMgmtService =
947 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600948 auto method = bus.new_method_call(
949 ldapMgmtService.c_str(), ldapMgrObjBasePath,
950 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
951
952 auto reply = bus.call(method);
953 reply.read(objects);
954 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600955 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600956 {
957 log<level::ERR>("Unable to get the User Service",
958 entry("WHAT=%s", e.what()));
959 throw;
960 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -0500961 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600962 {
963 log<level::ERR>(
964 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
965 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
966 throw;
967 }
968 return objects;
969}
970
Patrick Williams9638afb2021-02-22 17:16:24 -0600971std::string UserMgr::getLdapGroupName(const std::string& userName)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600972{
973 struct passwd pwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600974 {};
975 struct passwd* pwdPtr = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600976 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
977 if (buflen < -1)
978 {
979 // Use a default size if there is no hard limit suggested by sysconf()
980 buflen = 1024;
981 }
982 std::vector<char> buffer(buflen);
983 gid_t gid = 0;
984
985 auto status =
986 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
987 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
988 // If no matching password record was found, these functions return 0
989 // and store NULL in *pwdPtr
990 if (!status && (&pwd == pwdPtr))
991 {
992 gid = pwd.pw_gid;
993 }
994 else
995 {
996 log<level::ERR>("User does not exist",
997 entry("USER_NAME=%s", userName.c_str()));
998 elog<UserNameDoesNotExist>();
999 }
1000
Patrick Williams9638afb2021-02-22 17:16:24 -06001001 struct group* groups = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001002 std::string ldapGroupName;
1003
1004 while ((groups = getgrent()) != NULL)
1005 {
1006 if (groups->gr_gid == gid)
1007 {
1008 ldapGroupName = groups->gr_name;
1009 break;
1010 }
1011 }
1012 // Call endgrent() to close the group database.
1013 endgrent();
1014
1015 return ldapGroupName;
1016}
1017
Patrick Williams9638afb2021-02-22 17:16:24 -06001018std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001019{
1020 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1021 objMapperInterface, "GetObject");
1022
1023 mapperCall.append(std::move(path));
1024 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1025
1026 auto mapperResponseMsg = bus.call(mapperCall);
1027
1028 if (mapperResponseMsg.is_method_error())
1029 {
1030 log<level::ERR>("Error in mapper call");
1031 elog<InternalFailure>();
1032 }
1033
1034 std::map<std::string, std::vector<std::string>> mapperResponse;
1035 mapperResponseMsg.read(mapperResponse);
1036
1037 if (mapperResponse.begin() == mapperResponse.end())
1038 {
1039 log<level::ERR>("Invalid response from mapper");
1040 elog<InternalFailure>();
1041 }
1042
1043 return mapperResponse.begin()->first;
1044}
1045
1046UserInfoMap UserMgr::getUserInfo(std::string userName)
1047{
1048 UserInfoMap userInfo;
1049 // Check whether the given user is local user or not.
1050 if (isUserExist(userName) == true)
1051 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001052 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001053 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1054 userInfo.emplace("UserGroups", user.get()->userGroups());
1055 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1056 userInfo.emplace("UserLockedForFailedAttempt",
1057 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001058 userInfo.emplace("UserPasswordExpired",
1059 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001060 userInfo.emplace("RemoteUser", false);
1061 }
1062 else
1063 {
1064 std::string ldapGroupName = getLdapGroupName(userName);
1065 if (ldapGroupName.empty())
1066 {
1067 log<level::ERR>("Unable to get group name",
1068 entry("USER_NAME=%s", userName.c_str()));
1069 elog<InternalFailure>();
1070 }
1071
1072 DbusUserObj objects = getPrivilegeMapperObject();
1073
Ravi Teja5fe724a2019-05-07 05:14:42 -05001074 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001075 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001076
1077 try
1078 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001079 for (const auto& obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001080 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001081 for (const auto& interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001082 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001083 if ((interface.first ==
1084 "xyz.openbmc_project.Object.Enable"))
1085 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001086 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001087 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001088 auto value = std::get<bool>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001089 if ((property.first == "Enabled") &&
1090 (value == true))
1091 {
1092 ldapConfigPath = obj.first;
1093 break;
1094 }
1095 }
1096 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001097 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001098 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001099 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001100 break;
1101 }
1102 }
1103
1104 if (ldapConfigPath.empty())
1105 {
1106 return userInfo;
1107 }
1108
Patrick Williams9638afb2021-02-22 17:16:24 -06001109 for (const auto& obj : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001110 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001111 for (const auto& interface : obj.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001112 {
1113 if ((interface.first ==
1114 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1115 (obj.first.str.find(ldapConfigPath) !=
1116 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001117 {
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001118 std::string privilege;
1119 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001120
Patrick Williams9638afb2021-02-22 17:16:24 -06001121 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001122 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001123 auto value = std::get<std::string>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001124 if (property.first == "GroupName")
1125 {
1126 groupName = value;
1127 }
1128 else if (property.first == "Privilege")
1129 {
1130 privilege = value;
1131 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001132 }
1133 if (groupName == ldapGroupName)
1134 {
1135 userPrivilege = privilege;
1136 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001137 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001138 }
1139 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001140 if (!userPrivilege.empty())
1141 {
1142 break;
1143 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001144 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001145
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001146 if (userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001147 {
1148 log<level::ERR>("LDAP group privilege mapping does not exist");
1149 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001150 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001151 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001152 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001153 {
1154 log<level::ERR>("Error while accessing variant",
1155 entry("WHAT=%s", e.what()));
1156 elog<InternalFailure>();
1157 }
1158 userInfo.emplace("RemoteUser", true);
1159 }
1160
1161 return userInfo;
1162}
1163
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301164void UserMgr::initUserObjects(void)
1165{
1166 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001167 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301168 std::vector<std::string> userNameList;
1169 std::vector<std::string> sshGrpUsersList;
1170 UserSSHLists userSSHLists = getUserAndSshGrpList();
1171 userNameList = std::move(userSSHLists.first);
1172 sshGrpUsersList = std::move(userSSHLists.second);
1173
1174 if (!userNameList.empty())
1175 {
1176 std::map<std::string, std::vector<std::string>> groupLists;
Patrick Williams9638afb2021-02-22 17:16:24 -06001177 for (auto& grp : groupsMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301178 {
1179 if (grp == grpSsh)
1180 {
1181 groupLists.emplace(grp, sshGrpUsersList);
1182 }
1183 else
1184 {
1185 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1186 groupLists.emplace(grp, grpUsersList);
1187 }
1188 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001189 for (auto& grp : privMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301190 {
1191 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1192 groupLists.emplace(grp, grpUsersList);
1193 }
1194
Patrick Williams9638afb2021-02-22 17:16:24 -06001195 for (auto& user : userNameList)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301196 {
1197 std::vector<std::string> userGroups;
1198 std::string userPriv;
Patrick Williams9638afb2021-02-22 17:16:24 -06001199 for (const auto& grp : groupLists)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301200 {
1201 std::vector<std::string> tempGrp = grp.second;
1202 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1203 tempGrp.end())
1204 {
1205 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1206 privMgr.end())
1207 {
1208 userPriv = grp.first;
1209 }
1210 else
1211 {
1212 userGroups.emplace_back(grp.first);
1213 }
1214 }
1215 }
1216 // Add user objects to the Users path.
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +05301217 sdbusplus::message::object_path tempObjPath(usersObjPath);
1218 tempObjPath /= user;
1219 std::string objPath(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301220 std::sort(userGroups.begin(), userGroups.end());
Nan Zhou78d85042022-08-29 17:50:22 +00001221 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1222 bus, objPath.c_str(), userGroups,
1223 userPriv, isUserEnabled(user), *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301224 }
1225 }
1226}
1227
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001228UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
Patrick Williams224559b2022-04-05 16:10:39 -05001229 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301230{
1231 UserMgrIface::allPrivileges(privMgr);
1232 std::sort(groupsMgr.begin(), groupsMgr.end());
1233 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301234 std::string valueStr;
1235 auto value = minPasswdLength;
1236 unsigned long tmp = 0;
1237 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1238 success)
1239 {
1240 AccountPolicyIface::minPasswordLength(minPasswdLength);
1241 }
1242 else
1243 {
1244 try
1245 {
1246 tmp = std::stoul(valueStr, nullptr);
1247 if (tmp > std::numeric_limits<decltype(value)>::max())
1248 {
1249 throw std::out_of_range("Out of range");
1250 }
1251 value = static_cast<decltype(value)>(tmp);
1252 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001253 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301254 {
1255 log<level::ERR>("Exception for MinPasswordLength",
1256 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001257 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301258 }
1259 AccountPolicyIface::minPasswordLength(value);
1260 }
1261 valueStr.clear();
1262 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1263 success)
1264 {
1265 AccountPolicyIface::rememberOldPasswordTimes(0);
1266 }
1267 else
1268 {
1269 value = 0;
1270 try
1271 {
1272 tmp = std::stoul(valueStr, nullptr);
1273 if (tmp > std::numeric_limits<decltype(value)>::max())
1274 {
1275 throw std::out_of_range("Out of range");
1276 }
1277 value = static_cast<decltype(value)>(tmp);
1278 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001279 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301280 {
1281 log<level::ERR>("Exception for RememberOldPasswordTimes",
1282 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001283 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301284 }
1285 AccountPolicyIface::rememberOldPasswordTimes(value);
1286 }
1287 valueStr.clear();
1288 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1289 {
1290 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1291 }
1292 else
1293 {
1294 uint16_t value16 = 0;
1295 try
1296 {
1297 tmp = std::stoul(valueStr, nullptr);
1298 if (tmp > std::numeric_limits<decltype(value16)>::max())
1299 {
1300 throw std::out_of_range("Out of range");
1301 }
1302 value16 = static_cast<decltype(value16)>(tmp);
1303 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001304 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301305 {
1306 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1307 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001308 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301309 }
1310 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1311 }
1312 valueStr.clear();
1313 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1314 {
1315 AccountPolicyIface::accountUnlockTimeout(0);
1316 }
1317 else
1318 {
1319 uint32_t value32 = 0;
1320 try
1321 {
1322 tmp = std::stoul(valueStr, nullptr);
1323 if (tmp > std::numeric_limits<decltype(value32)>::max())
1324 {
1325 throw std::out_of_range("Out of range");
1326 }
1327 value32 = static_cast<decltype(value32)>(tmp);
1328 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001329 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301330 {
1331 log<level::ERR>("Exception for AccountUnlockTimeout",
1332 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001333 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301334 }
1335 AccountPolicyIface::accountUnlockTimeout(value32);
1336 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301337 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301338
1339 // emit the signal
1340 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301341}
1342
1343} // namespace user
1344} // namespace phosphor