blob: f2c500c6753eba8a4e20ea7c22e0c1de07cf4507 [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";
Nan Zhoue48085d2022-10-25 00:07:04 +000075static constexpr const char* defaultPamPasswdConfigFile =
76 "/etc/pam.d/common-password";
77static constexpr const char* defaultPamAuthConfigFile =
78 "/etc/pam.d/common-auth";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053079
Ratan Guptaaeaf9412019-02-11 04:41:52 -060080// Object Manager related
Patrick Williams9638afb2021-02-22 17:16:24 -060081static constexpr const char* ldapMgrObjBasePath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060082 "/xyz/openbmc_project/user/ldap";
83
84// Object Mapper related
Patrick Williams9638afb2021-02-22 17:16:24 -060085static constexpr const char* objMapperService =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060086 "xyz.openbmc_project.ObjectMapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060087static constexpr const char* objMapperPath =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060088 "/xyz/openbmc_project/object_mapper";
Patrick Williams9638afb2021-02-22 17:16:24 -060089static constexpr const char* objMapperInterface =
Ratan Guptaaeaf9412019-02-11 04:41:52 -060090 "xyz.openbmc_project.ObjectMapper";
91
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +053092using namespace phosphor::logging;
93using InsufficientPermission =
94 sdbusplus::xyz::openbmc_project::Common::Error::InsufficientPermission;
95using InternalFailure =
96 sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
97using InvalidArgument =
98 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
99using UserNameExists =
100 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameExists;
101using UserNameDoesNotExist =
102 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameDoesNotExist;
103using UserNameGroupFail =
104 sdbusplus::xyz::openbmc_project::User::Common::Error::UserNameGroupFail;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530105using NoResource =
106 sdbusplus::xyz::openbmc_project::User::Common::Error::NoResource;
107
108using Argument = xyz::openbmc_project::Common::InvalidArgument;
109
110template <typename... ArgTypes>
Patrick Williams9638afb2021-02-22 17:16:24 -0600111static std::vector<std::string> executeCmd(const char* path,
112 ArgTypes&&... tArgs)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530113{
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530114 std::vector<std::string> stdOutput;
115 boost::process::ipstream stdOutStream;
Patrick Williams9638afb2021-02-22 17:16:24 -0600116 boost::process::child execProg(path, const_cast<char*>(tArgs)...,
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530117 boost::process::std_out > stdOutStream);
118 std::string stdOutLine;
119
120 while (stdOutStream && std::getline(stdOutStream, stdOutLine) &&
121 !stdOutLine.empty())
122 {
123 stdOutput.emplace_back(stdOutLine);
124 }
125
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530126 execProg.wait();
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530127
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530128 int retCode = execProg.exit_code();
129 if (retCode)
130 {
Jonathan Domanccd28892021-10-14 16:43:33 -0700131 log<level::ERR>("Command execution failed", entry("PATH=%s", path),
132 entry("RETURN_CODE=%d", retCode));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530133 elog<InternalFailure>();
134 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530135
136 return stdOutput;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530137}
138
Nan Zhoue47c09d2022-10-25 00:06:41 +0000139std::string getCSVFromVector(std::span<const std::string> vec)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530140{
Nan Zhoue47c09d2022-10-25 00:06:41 +0000141 if (vec.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530142 {
Nan Zhoue47c09d2022-10-25 00:06:41 +0000143 return "";
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530144 }
Nan Zhoue47c09d2022-10-25 00:06:41 +0000145 return std::accumulate(std::next(vec.begin()), vec.end(), vec[0],
146 [](std::string&& val, std::string_view element) {
147 val += ',';
148 val += element;
149 return val;
150 });
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530151}
152
Nan Zhou332fb9d2022-10-25 00:07:03 +0000153bool removeStringFromCSV(std::string& csvStr, const std::string& delStr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530154{
155 std::string::size_type delStrPos = csvStr.find(delStr);
156 if (delStrPos != std::string::npos)
157 {
158 // need to also delete the comma char
159 if (delStrPos == 0)
160 {
161 csvStr.erase(delStrPos, delStr.size() + 1);
162 }
163 else
164 {
165 csvStr.erase(delStrPos - 1, delStr.size() + 1);
166 }
167 return true;
168 }
169 return false;
170}
171
Patrick Williams9638afb2021-02-22 17:16:24 -0600172bool UserMgr::isUserExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530173{
174 if (userName.empty())
175 {
176 log<level::ERR>("User name is empty");
177 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
178 Argument::ARGUMENT_VALUE("Null"));
179 }
180 if (usersList.find(userName) == usersList.end())
181 {
182 return false;
183 }
184 return true;
185}
186
Patrick Williams9638afb2021-02-22 17:16:24 -0600187void UserMgr::throwForUserDoesNotExist(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530188{
Nan Zhou8a11d992022-10-25 00:07:06 +0000189 if (!isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530190 {
191 log<level::ERR>("User does not exist",
192 entry("USER_NAME=%s", userName.c_str()));
193 elog<UserNameDoesNotExist>();
194 }
195}
196
Patrick Williams9638afb2021-02-22 17:16:24 -0600197void UserMgr::throwForUserExists(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530198{
Nan Zhou8a11d992022-10-25 00:07:06 +0000199 if (isUserExist(userName))
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530200 {
201 log<level::ERR>("User already exists",
202 entry("USER_NAME=%s", userName.c_str()));
203 elog<UserNameExists>();
204 }
205}
206
207void UserMgr::throwForUserNameConstraints(
Patrick Williams9638afb2021-02-22 17:16:24 -0600208 const std::string& userName, const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530209{
210 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
211 groupNames.end())
212 {
213 if (userName.length() > ipmiMaxUserNameLen)
214 {
215 log<level::ERR>("IPMI user name length limitation",
216 entry("SIZE=%d", userName.length()));
217 elog<UserNameGroupFail>(
218 xyz::openbmc_project::User::Common::UserNameGroupFail::REASON(
219 "IPMI length"));
220 }
221 }
222 if (userName.length() > systemMaxUserNameLen)
223 {
224 log<level::ERR>("User name length limitation",
225 entry("SIZE=%d", userName.length()));
226 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
227 Argument::ARGUMENT_VALUE("Invalid length"));
228 }
229 if (!std::regex_match(userName.c_str(),
230 std::regex("[a-zA-z_][a-zA-Z_0-9]*")))
231 {
232 log<level::ERR>("Invalid user name",
233 entry("USER_NAME=%s", userName.c_str()));
234 elog<InvalidArgument>(Argument::ARGUMENT_NAME("User name"),
235 Argument::ARGUMENT_VALUE("Invalid data"));
236 }
237}
238
239void UserMgr::throwForMaxGrpUserCount(
Patrick Williams9638afb2021-02-22 17:16:24 -0600240 const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530241{
242 if (std::find(groupNames.begin(), groupNames.end(), "ipmi") !=
243 groupNames.end())
244 {
245 if (getIpmiUsersCount() >= ipmiMaxUsers)
246 {
247 log<level::ERR>("IPMI user limit reached");
248 elog<NoResource>(
249 xyz::openbmc_project::User::Common::NoResource::REASON(
250 "ipmi user count reached"));
251 }
252 }
253 else
254 {
255 if (usersList.size() > 0 && (usersList.size() - getIpmiUsersCount()) >=
256 (maxSystemUsers - ipmiMaxUsers))
257 {
258 log<level::ERR>("Non-ipmi User limit reached");
259 elog<NoResource>(
260 xyz::openbmc_project::User::Common::NoResource::REASON(
261 "Non-ipmi user count reached"));
262 }
263 }
264 return;
265}
266
Patrick Williams9638afb2021-02-22 17:16:24 -0600267void UserMgr::throwForInvalidPrivilege(const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530268{
269 if (!priv.empty() &&
270 (std::find(privMgr.begin(), privMgr.end(), priv) == privMgr.end()))
271 {
272 log<level::ERR>("Invalid privilege");
273 elog<InvalidArgument>(Argument::ARGUMENT_NAME("Privilege"),
274 Argument::ARGUMENT_VALUE(priv.c_str()));
275 }
276}
277
Patrick Williams9638afb2021-02-22 17:16:24 -0600278void UserMgr::throwForInvalidGroups(const std::vector<std::string>& groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530279{
Patrick Williams9638afb2021-02-22 17:16:24 -0600280 for (auto& group : groupNames)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530281 {
282 if (std::find(groupsMgr.begin(), groupsMgr.end(), group) ==
283 groupsMgr.end())
284 {
285 log<level::ERR>("Invalid Group Name listed");
286 elog<InvalidArgument>(Argument::ARGUMENT_NAME("GroupName"),
287 Argument::ARGUMENT_VALUE(group.c_str()));
288 }
289 }
290}
291
292void UserMgr::createUser(std::string userName,
293 std::vector<std::string> groupNames, std::string priv,
294 bool enabled)
295{
296 throwForInvalidPrivilege(priv);
297 throwForInvalidGroups(groupNames);
298 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500299 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530300 throwForUserExists(userName);
301 throwForUserNameConstraints(userName, groupNames);
302 throwForMaxGrpUserCount(groupNames);
303
304 std::string groups = getCSVFromVector(groupNames);
305 bool sshRequested = removeStringFromCSV(groups, grpSsh);
306
307 // treat privilege as a group - This is to avoid using different file to
308 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530309 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530310 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530311 if (groups.size() != 0)
312 {
313 groups += ",";
314 }
315 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530316 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530317 try
318 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800319 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
320 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530321 executeCmd("/usr/sbin/useradd", userName.c_str(), "-G", groups.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530322 "-m", "-N", "-s",
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530323 (sshRequested ? "/bin/sh" : "/bin/nologin"), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800324 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530325 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600326 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530327 {
328 log<level::ERR>("Unable to create new user");
329 elog<InternalFailure>();
330 }
331
332 // Add the users object before sending out the signal
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530333 sdbusplus::message::object_path tempObjPath(usersObjPath);
334 tempObjPath /= userName;
335 std::string userObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530336 std::sort(groupNames.begin(), groupNames.end());
337 usersList.emplace(
Nan Zhou78d85042022-08-29 17:50:22 +0000338 userName, std::make_unique<phosphor::user::Users>(
339 bus, userObj.c_str(), groupNames, priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530340
341 log<level::INFO>("User created successfully",
342 entry("USER_NAME=%s", userName.c_str()));
343 return;
344}
345
346void UserMgr::deleteUser(std::string userName)
347{
348 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500349 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530350 throwForUserDoesNotExist(userName);
351 try
352 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530353 executeCmd("/usr/sbin/userdel", userName.c_str(), "-r");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530354 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600355 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530356 {
357 log<level::ERR>("User delete failed",
358 entry("USER_NAME=%s", userName.c_str()));
359 elog<InternalFailure>();
360 }
361
362 usersList.erase(userName);
363
364 log<level::INFO>("User deleted successfully",
365 entry("USER_NAME=%s", userName.c_str()));
366 return;
367}
368
369void UserMgr::renameUser(std::string userName, std::string newUserName)
370{
371 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500372 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530373 throwForUserDoesNotExist(userName);
374 throwForUserExists(newUserName);
375 throwForUserNameConstraints(newUserName,
376 usersList[userName].get()->userGroups());
377 try
378 {
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530379 std::string newHomeDir = "/home/" + newUserName;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530380 executeCmd("/usr/sbin/usermod", "-l", newUserName.c_str(),
Richard Marian Thomaiyarf977b1a2018-07-16 23:50:51 +0530381 userName.c_str(), "-d", newHomeDir.c_str(), "-m");
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530382 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600383 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530384 {
385 log<level::ERR>("User rename failed",
386 entry("USER_NAME=%s", userName.c_str()));
387 elog<InternalFailure>();
388 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600389 const auto& user = usersList[userName];
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530390 std::string priv = user.get()->userPrivilege();
391 std::vector<std::string> groupNames = user.get()->userGroups();
392 bool enabled = user.get()->userEnabled();
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +0530393 sdbusplus::message::object_path tempObjPath(usersObjPath);
394 tempObjPath /= newUserName;
395 std::string newUserObj(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530396 // Special group 'ipmi' needs a way to identify user renamed, in order to
397 // update encrypted password. It can't rely only on InterfacesRemoved &
398 // InterfacesAdded. So first send out userRenamed signal.
399 this->userRenamed(userName, newUserName);
400 usersList.erase(userName);
Nan Zhou78d85042022-08-29 17:50:22 +0000401 usersList.emplace(newUserName, std::make_unique<phosphor::user::Users>(
402 bus, newUserObj.c_str(), groupNames,
403 priv, enabled, *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530404 return;
405}
406
Patrick Williams9638afb2021-02-22 17:16:24 -0600407void UserMgr::updateGroupsAndPriv(const std::string& userName,
408 const std::vector<std::string>& groupNames,
409 const std::string& priv)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530410{
411 throwForInvalidPrivilege(priv);
412 throwForInvalidGroups(groupNames);
413 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500414 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530415 throwForUserDoesNotExist(userName);
Patrick Williams9638afb2021-02-22 17:16:24 -0600416 const std::vector<std::string>& oldGroupNames =
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530417 usersList[userName].get()->userGroups();
418 std::vector<std::string> groupDiff;
419 // Note: already dealing with sorted group lists.
420 std::set_symmetric_difference(oldGroupNames.begin(), oldGroupNames.end(),
421 groupNames.begin(), groupNames.end(),
422 std::back_inserter(groupDiff));
423 if (std::find(groupDiff.begin(), groupDiff.end(), "ipmi") !=
424 groupDiff.end())
425 {
426 throwForUserNameConstraints(userName, groupNames);
427 throwForMaxGrpUserCount(groupNames);
428 }
429
430 std::string groups = getCSVFromVector(groupNames);
431 bool sshRequested = removeStringFromCSV(groups, grpSsh);
432
433 // treat privilege as a group - This is to avoid using different file to
434 // store the same.
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530435 if (!priv.empty())
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530436 {
Richard Marian Thomaiyar2cb2e722018-09-27 14:22:42 +0530437 if (groups.size() != 0)
438 {
439 groups += ",";
440 }
441 groups += priv;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530442 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530443 try
444 {
445 executeCmd("/usr/sbin/usermod", userName.c_str(), "-G", groups.c_str(),
446 "-s", (sshRequested ? "/bin/sh" : "/bin/nologin"));
447 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600448 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530449 {
450 log<level::ERR>("Unable to modify user privilege / groups");
451 elog<InternalFailure>();
452 }
453
454 log<level::INFO>("User groups / privilege updated successfully",
455 entry("USER_NAME=%s", userName.c_str()));
456 return;
457}
458
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530459uint8_t UserMgr::minPasswordLength(uint8_t value)
460{
461 if (value == AccountPolicyIface::minPasswordLength())
462 {
463 return value;
464 }
465 if (value < minPasswdLength)
466 {
Paul Fertser6dc7ed92022-01-21 19:36:34 +0000467 log<level::ERR>(("Attempting to set minPasswordLength to less than " +
468 std::to_string(minPasswdLength))
469 .c_str(),
470 entry("SIZE=%d", value));
471 elog<InvalidArgument>(
472 Argument::ARGUMENT_NAME("minPasswordLength"),
473 Argument::ARGUMENT_VALUE(std::to_string(value).c_str()));
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530474 }
475 if (setPamModuleArgValue(pamCrackLib, minPasswdLenProp,
476 std::to_string(value)) != success)
477 {
478 log<level::ERR>("Unable to set minPasswordLength");
479 elog<InternalFailure>();
480 }
481 return AccountPolicyIface::minPasswordLength(value);
482}
483
484uint8_t UserMgr::rememberOldPasswordTimes(uint8_t value)
485{
486 if (value == AccountPolicyIface::rememberOldPasswordTimes())
487 {
488 return value;
489 }
490 if (setPamModuleArgValue(pamPWHistory, remOldPasswdCount,
491 std::to_string(value)) != success)
492 {
493 log<level::ERR>("Unable to set rememberOldPasswordTimes");
494 elog<InternalFailure>();
495 }
496 return AccountPolicyIface::rememberOldPasswordTimes(value);
497}
498
499uint16_t UserMgr::maxLoginAttemptBeforeLockout(uint16_t value)
500{
501 if (value == AccountPolicyIface::maxLoginAttemptBeforeLockout())
502 {
503 return value;
504 }
505 if (setPamModuleArgValue(pamTally2, maxFailedAttempt,
506 std::to_string(value)) != success)
507 {
508 log<level::ERR>("Unable to set maxLoginAttemptBeforeLockout");
509 elog<InternalFailure>();
510 }
511 return AccountPolicyIface::maxLoginAttemptBeforeLockout(value);
512}
513
514uint32_t UserMgr::accountUnlockTimeout(uint32_t value)
515{
516 if (value == AccountPolicyIface::accountUnlockTimeout())
517 {
518 return value;
519 }
520 if (setPamModuleArgValue(pamTally2, unlockTimeout, std::to_string(value)) !=
521 success)
522 {
523 log<level::ERR>("Unable to set accountUnlockTimeout");
524 elog<InternalFailure>();
525 }
526 return AccountPolicyIface::accountUnlockTimeout(value);
527}
528
Patrick Williams9638afb2021-02-22 17:16:24 -0600529int UserMgr::getPamModuleArgValue(const std::string& moduleName,
530 const std::string& argName,
531 std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530532{
533 std::string fileName;
534 if (moduleName == pamTally2)
535 {
536 fileName = pamAuthConfigFile;
537 }
538 else
539 {
540 fileName = pamPasswdConfigFile;
541 }
542 std::ifstream fileToRead(fileName, std::ios::in);
543 if (!fileToRead.is_open())
544 {
545 log<level::ERR>("Failed to open pam configuration file",
546 entry("FILE_NAME=%s", fileName.c_str()));
547 return failure;
548 }
549 std::string line;
550 auto argSearch = argName + "=";
551 size_t startPos = 0;
552 size_t endPos = 0;
553 while (getline(fileToRead, line))
554 {
555 // skip comments section starting with #
556 if ((startPos = line.find('#')) != std::string::npos)
557 {
558 if (startPos == 0)
559 {
560 continue;
561 }
562 // skip comments after meaningful section and process those
563 line = line.substr(0, startPos);
564 }
565 if (line.find(moduleName) != std::string::npos)
566 {
567 if ((startPos = line.find(argSearch)) != std::string::npos)
568 {
569 if ((endPos = line.find(' ', startPos)) == std::string::npos)
570 {
571 endPos = line.size();
572 }
573 startPos += argSearch.size();
574 argValue = line.substr(startPos, endPos - startPos);
575 return success;
576 }
577 }
578 }
579 return failure;
580}
581
Patrick Williams9638afb2021-02-22 17:16:24 -0600582int UserMgr::setPamModuleArgValue(const std::string& moduleName,
583 const std::string& argName,
584 const std::string& argValue)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +0530585{
586 std::string fileName;
587 if (moduleName == pamTally2)
588 {
589 fileName = pamAuthConfigFile;
590 }
591 else
592 {
593 fileName = pamPasswdConfigFile;
594 }
595 std::string tmpFileName = fileName + "_tmp";
596 std::ifstream fileToRead(fileName, std::ios::in);
597 std::ofstream fileToWrite(tmpFileName, std::ios::out);
598 if (!fileToRead.is_open() || !fileToWrite.is_open())
599 {
600 log<level::ERR>("Failed to open pam configuration /tmp file",
601 entry("FILE_NAME=%s", fileName.c_str()));
602 return failure;
603 }
604 std::string line;
605 auto argSearch = argName + "=";
606 size_t startPos = 0;
607 size_t endPos = 0;
608 bool found = false;
609 while (getline(fileToRead, line))
610 {
611 // skip comments section starting with #
612 if ((startPos = line.find('#')) != std::string::npos)
613 {
614 if (startPos == 0)
615 {
616 fileToWrite << line << std::endl;
617 continue;
618 }
619 // skip comments after meaningful section and process those
620 line = line.substr(0, startPos);
621 }
622 if (line.find(moduleName) != std::string::npos)
623 {
624 if ((startPos = line.find(argSearch)) != std::string::npos)
625 {
626 if ((endPos = line.find(' ', startPos)) == std::string::npos)
627 {
628 endPos = line.size();
629 }
630 startPos += argSearch.size();
631 fileToWrite << line.substr(0, startPos) << argValue
632 << line.substr(endPos, line.size() - endPos)
633 << std::endl;
634 found = true;
635 continue;
636 }
637 }
638 fileToWrite << line << std::endl;
639 }
640 fileToWrite.close();
641 fileToRead.close();
642 if (found)
643 {
644 if (std::rename(tmpFileName.c_str(), fileName.c_str()) == 0)
645 {
646 return success;
647 }
648 }
649 return failure;
650}
651
Patrick Williams9638afb2021-02-22 17:16:24 -0600652void UserMgr::userEnable(const std::string& userName, bool enabled)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530653{
654 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500655 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530656 throwForUserDoesNotExist(userName);
657 try
658 {
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800659 // set EXPIRE_DATE to 0 to disable user, PAM takes 0 as expire on
660 // 1970-01-01, that's an implementation-defined behavior
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530661 executeCmd("/usr/sbin/usermod", userName.c_str(), "-e",
Jiaqing Zhaoce89bfc2021-12-02 21:26:01 +0800662 (enabled ? "" : "1970-01-01"));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530663 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600664 catch (const InternalFailure& e)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530665 {
666 log<level::ERR>("Unable to modify user enabled state");
667 elog<InternalFailure>();
668 }
669
670 log<level::INFO>("User enabled/disabled state updated successfully",
671 entry("USER_NAME=%s", userName.c_str()),
672 entry("ENABLED=%d", enabled));
673 return;
674}
675
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530676/**
677 * pam_tally2 app will provide the user failure count and failure status
678 * in second line of output with words position [0] - user name,
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800679 * [1] - failure count, [2] - latest failure date, [3] - latest failure time
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530680 * [4] - failure app
681 **/
682
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530683static constexpr size_t t2FailCntIdx = 1;
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800684static constexpr size_t t2FailDateIdx = 2;
685static constexpr size_t t2FailTimeIdx = 3;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530686static constexpr size_t t2OutputIndex = 1;
687
Patrick Williams9638afb2021-02-22 17:16:24 -0600688bool UserMgr::userLockedForFailedAttempt(const std::string& userName)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530689{
690 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500691 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800692 if (AccountPolicyIface::maxLoginAttemptBeforeLockout() == 0)
693 {
694 return false;
695 }
696
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530697 std::vector<std::string> output;
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800698 try
699 {
700 output = executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str());
701 }
702 catch (const InternalFailure& e)
703 {
704 log<level::ERR>("Unable to read login failure counter");
705 elog<InternalFailure>();
706 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530707
708 std::vector<std::string> splitWords;
709 boost::algorithm::split(splitWords, output[t2OutputIndex],
710 boost::algorithm::is_any_of("\t "),
711 boost::token_compress_on);
712
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800713 uint16_t failAttempts = 0;
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530714 try
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530715 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530716 unsigned long tmp = std::stoul(splitWords[t2FailCntIdx], nullptr);
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800717 if (tmp > std::numeric_limits<decltype(failAttempts)>::max())
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530718 {
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530719 throw std::out_of_range("Out of range");
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530720 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800721 failAttempts = static_cast<decltype(failAttempts)>(tmp);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530722 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600723 catch (const std::exception& e)
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530724 {
725 log<level::ERR>("Exception for userLockedForFailedAttempt",
726 entry("WHAT=%s", e.what()));
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800727 elog<InternalFailure>();
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530728 }
Jiaqing Zhaofba4bb12022-06-24 01:30:57 +0800729
730 if (failAttempts < AccountPolicyIface::maxLoginAttemptBeforeLockout())
731 {
732 return false;
733 }
734
735 // When failedAttempts is not 0, Latest failure date/time should be
736 // available
737 if (splitWords.size() < 4)
738 {
739 log<level::ERR>("Unable to read latest failure date/time");
740 elog<InternalFailure>();
741 }
742
743 const std::string failDateTime =
744 splitWords[t2FailDateIdx] + ' ' + splitWords[t2FailTimeIdx];
745
746 // NOTE: Cannot use std::get_time() here as the implementation of %y in
747 // libstdc++ does not match POSIX strptime() before gcc 12.1.0
748 // https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=a8d3c98746098e2784be7144c1ccc9fcc34a0888
749 std::tm tmStruct = {};
750 if (!strptime(failDateTime.c_str(), "%D %H:%M:%S", &tmStruct))
751 {
752 log<level::ERR>("Failed to parse latest failure date/time");
753 elog<InternalFailure>();
754 }
755
756 time_t failTimestamp = std::mktime(&tmStruct);
757 if (failTimestamp +
758 static_cast<time_t>(AccountPolicyIface::accountUnlockTimeout()) <=
759 std::time(NULL))
760 {
761 return false;
762 }
763
764 return true;
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530765}
766
Patrick Williams9638afb2021-02-22 17:16:24 -0600767bool UserMgr::userLockedForFailedAttempt(const std::string& userName,
768 const bool& value)
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530769{
770 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500771 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530772 if (value == true)
773 {
774 return userLockedForFailedAttempt(userName);
775 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530776
Jiaqing Zhao8557d322022-06-23 23:00:01 +0800777 try
778 {
779 executeCmd("/usr/sbin/pam_tally2", "-u", userName.c_str(), "-r");
780 }
781 catch (const InternalFailure& e)
782 {
783 log<level::ERR>("Unable to reset login failure counter");
784 elog<InternalFailure>();
785 }
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530786
Richard Marian Thomaiyarf5c2df52018-11-22 23:24:25 +0530787 return userLockedForFailedAttempt(userName);
Richard Marian Thomaiyarc7045192018-06-13 16:51:00 +0530788}
789
Patrick Williams9638afb2021-02-22 17:16:24 -0600790bool UserMgr::userPasswordExpired(const std::string& userName)
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600791{
792 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500793 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600794
795 struct spwd spwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600796 {};
797 struct spwd* spwdPtr = nullptr;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600798 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
799 if (buflen < -1)
800 {
801 // Use a default size if there is no hard limit suggested by sysconf()
802 buflen = 1024;
803 }
804 std::vector<char> buffer(buflen);
805 auto status =
806 getspnam_r(userName.c_str(), &spwd, buffer.data(), buflen, &spwdPtr);
807 // On success, getspnam_r() returns zero, and sets *spwdPtr to spwd.
808 // If no matching password record was found, these functions return 0
809 // and store NULL in *spwdPtr
810 if ((status == 0) && (&spwd == spwdPtr))
811 {
812 // Determine password validity per "chage" docs, where:
813 // spwd.sp_lstchg == 0 means password is expired, and
814 // spwd.sp_max == -1 means the password does not expire.
Nan Zhou78d85042022-08-29 17:50:22 +0000815 constexpr long secondsPerDay = 60 * 60 * 24;
816 long today = static_cast<long>(time(NULL)) / secondsPerDay;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600817 if ((spwd.sp_lstchg == 0) ||
818 ((spwd.sp_max != -1) && ((spwd.sp_max + spwd.sp_lstchg) < today)))
819 {
820 return true;
821 }
822 }
823 else
824 {
Jayaprakash Mutyala75be4e62020-09-18 15:59:06 +0000825 // User entry is missing in /etc/shadow, indicating no SHA password.
826 // Treat this as new user without password entry in /etc/shadow
827 // TODO: Add property to indicate user password was not set yet
828 // https://github.com/openbmc/phosphor-user-manager/issues/8
829 return false;
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -0600830 }
831
832 return false;
833}
834
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530835UserSSHLists UserMgr::getUserAndSshGrpList()
836{
837 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500838 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530839
840 std::vector<std::string> userList;
841 std::vector<std::string> sshUsersList;
842 struct passwd pw, *pwp = nullptr;
843 std::array<char, 1024> buffer{};
844
845 phosphor::user::File passwd(passwdFileName, "r");
846 if ((passwd)() == NULL)
847 {
848 log<level::ERR>("Error opening the passwd file");
849 elog<InternalFailure>();
850 }
851
852 while (true)
853 {
854 auto r = fgetpwent_r((passwd)(), &pw, buffer.data(), buffer.max_size(),
855 &pwp);
856 if ((r != 0) || (pwp == NULL))
857 {
858 // Any error, break the loop.
859 break;
860 }
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530861#ifdef ENABLE_ROOT_USER_MGMT
Richard Marian Thomaiyar7ba3c712018-07-31 13:41:36 +0530862 // Add all users whose UID >= 1000 and < 65534
863 // and special UID 0.
864 if ((pwp->pw_uid == 0) ||
865 ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534)))
Richard Marian Thomaiyard4d65502019-11-02 21:02:03 +0530866#else
867 // Add all users whose UID >=1000 and < 65534
868 if ((pwp->pw_uid >= 1000) && (pwp->pw_uid < 65534))
869#endif
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530870 {
871 std::string userName(pwp->pw_name);
872 userList.emplace_back(userName);
873
874 // ssh doesn't have separate group. Check login shell entry to
875 // get all users list which are member of ssh group.
876 std::string loginShell(pwp->pw_shell);
877 if (loginShell == "/bin/sh")
878 {
879 sshUsersList.emplace_back(userName);
880 }
881 }
882 }
883 endpwent();
884 return std::make_pair(std::move(userList), std::move(sshUsersList));
885}
886
887size_t UserMgr::getIpmiUsersCount()
888{
889 std::vector<std::string> userList = getUsersInGroup("ipmi");
890 return userList.size();
891}
892
Patrick Williams9638afb2021-02-22 17:16:24 -0600893bool UserMgr::isUserEnabled(const std::string& userName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530894{
895 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -0500896 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530897 std::array<char, 4096> buffer{};
898 struct spwd spwd;
Patrick Williams9638afb2021-02-22 17:16:24 -0600899 struct spwd* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530900 int status = getspnam_r(userName.c_str(), &spwd, buffer.data(),
901 buffer.max_size(), &resultPtr);
902 if (!status && (&spwd == resultPtr))
903 {
904 if (resultPtr->sp_expire >= 0)
905 {
906 return false; // user locked out
907 }
908 return true;
909 }
910 return false; // assume user is disabled for any error.
911}
912
Patrick Williams9638afb2021-02-22 17:16:24 -0600913std::vector<std::string> UserMgr::getUsersInGroup(const std::string& groupName)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530914{
915 std::vector<std::string> usersInGroup;
916 // Should be more than enough to get the pwd structure.
917 std::array<char, 4096> buffer{};
918 struct group grp;
Patrick Williams9638afb2021-02-22 17:16:24 -0600919 struct group* resultPtr = nullptr;
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +0530920
921 int status = getgrnam_r(groupName.c_str(), &grp, buffer.data(),
922 buffer.max_size(), &resultPtr);
923
924 if (!status && (&grp == resultPtr))
925 {
926 for (; *(grp.gr_mem) != NULL; ++(grp.gr_mem))
927 {
928 usersInGroup.emplace_back(*(grp.gr_mem));
929 }
930 }
931 else
932 {
933 log<level::ERR>("Group not found",
934 entry("GROUP=%s", groupName.c_str()));
935 // Don't throw error, just return empty userList - fallback
936 }
937 return usersInGroup;
938}
939
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600940DbusUserObj UserMgr::getPrivilegeMapperObject(void)
941{
942 DbusUserObj objects;
943 try
944 {
Ravi Teja5fe724a2019-05-07 05:14:42 -0500945 std::string basePath = "/xyz/openbmc_project/user/ldap/openldap";
946 std::string interface = "xyz.openbmc_project.User.Ldap.Config";
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600947
948 auto ldapMgmtService =
949 getServiceName(std::move(basePath), std::move(interface));
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600950 auto method = bus.new_method_call(
951 ldapMgmtService.c_str(), ldapMgrObjBasePath,
952 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
953
954 auto reply = bus.call(method);
955 reply.read(objects);
956 }
Patrick Williams9638afb2021-02-22 17:16:24 -0600957 catch (const InternalFailure& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600958 {
959 log<level::ERR>("Unable to get the User Service",
960 entry("WHAT=%s", e.what()));
961 throw;
962 }
Patrick Williamsb3ef4e12022-07-22 19:26:55 -0500963 catch (const sdbusplus::exception_t& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600964 {
965 log<level::ERR>(
966 "Failed to excute method", entry("METHOD=%s", "GetManagedObjects"),
967 entry("PATH=%s", ldapMgrObjBasePath), entry("WHAT=%s", e.what()));
968 throw;
969 }
970 return objects;
971}
972
Patrick Williams9638afb2021-02-22 17:16:24 -0600973std::string UserMgr::getLdapGroupName(const std::string& userName)
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600974{
975 struct passwd pwd
Patrick Williams9638afb2021-02-22 17:16:24 -0600976 {};
977 struct passwd* pwdPtr = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -0600978 auto buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
979 if (buflen < -1)
980 {
981 // Use a default size if there is no hard limit suggested by sysconf()
982 buflen = 1024;
983 }
984 std::vector<char> buffer(buflen);
985 gid_t gid = 0;
986
987 auto status =
988 getpwnam_r(userName.c_str(), &pwd, buffer.data(), buflen, &pwdPtr);
989 // On success, getpwnam_r() returns zero, and set *pwdPtr to pwd.
990 // If no matching password record was found, these functions return 0
991 // and store NULL in *pwdPtr
992 if (!status && (&pwd == pwdPtr))
993 {
994 gid = pwd.pw_gid;
995 }
996 else
997 {
998 log<level::ERR>("User does not exist",
999 entry("USER_NAME=%s", userName.c_str()));
1000 elog<UserNameDoesNotExist>();
1001 }
1002
Patrick Williams9638afb2021-02-22 17:16:24 -06001003 struct group* groups = nullptr;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001004 std::string ldapGroupName;
1005
1006 while ((groups = getgrent()) != NULL)
1007 {
1008 if (groups->gr_gid == gid)
1009 {
1010 ldapGroupName = groups->gr_name;
1011 break;
1012 }
1013 }
1014 // Call endgrent() to close the group database.
1015 endgrent();
1016
1017 return ldapGroupName;
1018}
1019
Patrick Williams9638afb2021-02-22 17:16:24 -06001020std::string UserMgr::getServiceName(std::string&& path, std::string&& intf)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001021{
1022 auto mapperCall = bus.new_method_call(objMapperService, objMapperPath,
1023 objMapperInterface, "GetObject");
1024
1025 mapperCall.append(std::move(path));
1026 mapperCall.append(std::vector<std::string>({std::move(intf)}));
1027
1028 auto mapperResponseMsg = bus.call(mapperCall);
1029
1030 if (mapperResponseMsg.is_method_error())
1031 {
1032 log<level::ERR>("Error in mapper call");
1033 elog<InternalFailure>();
1034 }
1035
1036 std::map<std::string, std::vector<std::string>> mapperResponse;
1037 mapperResponseMsg.read(mapperResponse);
1038
1039 if (mapperResponse.begin() == mapperResponse.end())
1040 {
1041 log<level::ERR>("Invalid response from mapper");
1042 elog<InternalFailure>();
1043 }
1044
1045 return mapperResponse.begin()->first;
1046}
1047
1048UserInfoMap UserMgr::getUserInfo(std::string userName)
1049{
1050 UserInfoMap userInfo;
1051 // Check whether the given user is local user or not.
Nan Zhou8a11d992022-10-25 00:07:06 +00001052 if (isUserExist(userName))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001053 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001054 const auto& user = usersList[userName];
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001055 userInfo.emplace("UserPrivilege", user.get()->userPrivilege());
1056 userInfo.emplace("UserGroups", user.get()->userGroups());
1057 userInfo.emplace("UserEnabled", user.get()->userEnabled());
1058 userInfo.emplace("UserLockedForFailedAttempt",
1059 user.get()->userLockedForFailedAttempt());
Joseph Reynolds3ab6cc22020-03-03 14:09:03 -06001060 userInfo.emplace("UserPasswordExpired",
1061 user.get()->userPasswordExpired());
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001062 userInfo.emplace("RemoteUser", false);
1063 }
1064 else
1065 {
1066 std::string ldapGroupName = getLdapGroupName(userName);
1067 if (ldapGroupName.empty())
1068 {
1069 log<level::ERR>("Unable to get group name",
1070 entry("USER_NAME=%s", userName.c_str()));
1071 elog<InternalFailure>();
1072 }
1073
1074 DbusUserObj objects = getPrivilegeMapperObject();
1075
Ravi Teja5fe724a2019-05-07 05:14:42 -05001076 std::string ldapConfigPath;
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001077 std::string userPrivilege;
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001078
1079 try
1080 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001081 for (const auto& obj : objects)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001082 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001083 for (const auto& interface : obj.second)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001084 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001085 if ((interface.first ==
1086 "xyz.openbmc_project.Object.Enable"))
1087 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001088 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001089 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001090 auto value = std::get<bool>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001091 if ((property.first == "Enabled") &&
1092 (value == true))
1093 {
1094 ldapConfigPath = obj.first;
1095 break;
1096 }
1097 }
1098 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001099 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001100 if (!ldapConfigPath.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001101 {
Ravi Teja5fe724a2019-05-07 05:14:42 -05001102 break;
1103 }
1104 }
1105
1106 if (ldapConfigPath.empty())
1107 {
1108 return userInfo;
1109 }
1110
Patrick Williams9638afb2021-02-22 17:16:24 -06001111 for (const auto& obj : objects)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001112 {
Patrick Williams9638afb2021-02-22 17:16:24 -06001113 for (const auto& interface : obj.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001114 {
1115 if ((interface.first ==
1116 "xyz.openbmc_project.User.PrivilegeMapperEntry") &&
1117 (obj.first.str.find(ldapConfigPath) !=
1118 std::string::npos))
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001119 {
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001120 std::string privilege;
1121 std::string groupName;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001122
Patrick Williams9638afb2021-02-22 17:16:24 -06001123 for (const auto& property : interface.second)
Ravi Teja5fe724a2019-05-07 05:14:42 -05001124 {
Patrick Williams8f8fc232020-05-13 12:25:51 -05001125 auto value = std::get<std::string>(property.second);
Ravi Teja5fe724a2019-05-07 05:14:42 -05001126 if (property.first == "GroupName")
1127 {
1128 groupName = value;
1129 }
1130 else if (property.first == "Privilege")
1131 {
1132 privilege = value;
1133 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001134 }
1135 if (groupName == ldapGroupName)
1136 {
1137 userPrivilege = privilege;
1138 break;
Ravi Teja5fe724a2019-05-07 05:14:42 -05001139 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001140 }
1141 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001142 if (!userPrivilege.empty())
1143 {
1144 break;
1145 }
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001146 }
Ravi Teja5fe724a2019-05-07 05:14:42 -05001147
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001148 if (userPrivilege.empty())
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001149 {
1150 log<level::ERR>("LDAP group privilege mapping does not exist");
1151 }
Jiaqing Zhao745ce2e2022-05-31 17:11:31 +08001152 userInfo.emplace("UserPrivilege", userPrivilege);
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001153 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001154 catch (const std::bad_variant_access& e)
Ratan Guptaaeaf9412019-02-11 04:41:52 -06001155 {
1156 log<level::ERR>("Error while accessing variant",
1157 entry("WHAT=%s", e.what()));
1158 elog<InternalFailure>();
1159 }
1160 userInfo.emplace("RemoteUser", true);
1161 }
1162
1163 return userInfo;
1164}
1165
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301166void UserMgr::initUserObjects(void)
1167{
1168 // All user management lock has to be based on /etc/shadow
Andrew Geisslera260f182021-05-14 12:20:12 -05001169 // TODO phosphor-user-manager#10 phosphor::user::shadow::Lock lock{};
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301170 std::vector<std::string> userNameList;
1171 std::vector<std::string> sshGrpUsersList;
1172 UserSSHLists userSSHLists = getUserAndSshGrpList();
1173 userNameList = std::move(userSSHLists.first);
1174 sshGrpUsersList = std::move(userSSHLists.second);
1175
1176 if (!userNameList.empty())
1177 {
1178 std::map<std::string, std::vector<std::string>> groupLists;
Patrick Williams9638afb2021-02-22 17:16:24 -06001179 for (auto& grp : groupsMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301180 {
1181 if (grp == grpSsh)
1182 {
1183 groupLists.emplace(grp, sshGrpUsersList);
1184 }
1185 else
1186 {
1187 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1188 groupLists.emplace(grp, grpUsersList);
1189 }
1190 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001191 for (auto& grp : privMgr)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301192 {
1193 std::vector<std::string> grpUsersList = getUsersInGroup(grp);
1194 groupLists.emplace(grp, grpUsersList);
1195 }
1196
Patrick Williams9638afb2021-02-22 17:16:24 -06001197 for (auto& user : userNameList)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301198 {
1199 std::vector<std::string> userGroups;
1200 std::string userPriv;
Patrick Williams9638afb2021-02-22 17:16:24 -06001201 for (const auto& grp : groupLists)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301202 {
1203 std::vector<std::string> tempGrp = grp.second;
1204 if (std::find(tempGrp.begin(), tempGrp.end(), user) !=
1205 tempGrp.end())
1206 {
1207 if (std::find(privMgr.begin(), privMgr.end(), grp.first) !=
1208 privMgr.end())
1209 {
1210 userPriv = grp.first;
1211 }
1212 else
1213 {
1214 userGroups.emplace_back(grp.first);
1215 }
1216 }
1217 }
1218 // Add user objects to the Users path.
P Dheeraj Srujan Kumarb01e2fe2021-12-13 09:43:28 +05301219 sdbusplus::message::object_path tempObjPath(usersObjPath);
1220 tempObjPath /= user;
1221 std::string objPath(tempObjPath);
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301222 std::sort(userGroups.begin(), userGroups.end());
Nan Zhou78d85042022-08-29 17:50:22 +00001223 usersList.emplace(user, std::make_unique<phosphor::user::Users>(
1224 bus, objPath.c_str(), userGroups,
1225 userPriv, isUserEnabled(user), *this));
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301226 }
1227 }
1228}
1229
Patrick Williamsb3ef4e12022-07-22 19:26:55 -05001230UserMgr::UserMgr(sdbusplus::bus_t& bus, const char* path) :
Nan Zhoue48085d2022-10-25 00:07:04 +00001231 Ifaces(bus, path, Ifaces::action::defer_emit), bus(bus), path(path),
1232 pamPasswdConfigFile(defaultPamPasswdConfigFile),
1233 pamAuthConfigFile(defaultPamAuthConfigFile)
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301234{
1235 UserMgrIface::allPrivileges(privMgr);
1236 std::sort(groupsMgr.begin(), groupsMgr.end());
1237 UserMgrIface::allGroups(groupsMgr);
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301238 std::string valueStr;
1239 auto value = minPasswdLength;
1240 unsigned long tmp = 0;
1241 if (getPamModuleArgValue(pamCrackLib, minPasswdLenProp, valueStr) !=
1242 success)
1243 {
1244 AccountPolicyIface::minPasswordLength(minPasswdLength);
1245 }
1246 else
1247 {
1248 try
1249 {
1250 tmp = std::stoul(valueStr, nullptr);
1251 if (tmp > std::numeric_limits<decltype(value)>::max())
1252 {
1253 throw std::out_of_range("Out of range");
1254 }
1255 value = static_cast<decltype(value)>(tmp);
1256 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001257 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301258 {
1259 log<level::ERR>("Exception for MinPasswordLength",
1260 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001261 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301262 }
1263 AccountPolicyIface::minPasswordLength(value);
1264 }
1265 valueStr.clear();
1266 if (getPamModuleArgValue(pamPWHistory, remOldPasswdCount, valueStr) !=
1267 success)
1268 {
1269 AccountPolicyIface::rememberOldPasswordTimes(0);
1270 }
1271 else
1272 {
1273 value = 0;
1274 try
1275 {
1276 tmp = std::stoul(valueStr, nullptr);
1277 if (tmp > std::numeric_limits<decltype(value)>::max())
1278 {
1279 throw std::out_of_range("Out of range");
1280 }
1281 value = static_cast<decltype(value)>(tmp);
1282 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001283 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301284 {
1285 log<level::ERR>("Exception for RememberOldPasswordTimes",
1286 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001287 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301288 }
1289 AccountPolicyIface::rememberOldPasswordTimes(value);
1290 }
1291 valueStr.clear();
1292 if (getPamModuleArgValue(pamTally2, maxFailedAttempt, valueStr) != success)
1293 {
1294 AccountPolicyIface::maxLoginAttemptBeforeLockout(0);
1295 }
1296 else
1297 {
1298 uint16_t value16 = 0;
1299 try
1300 {
1301 tmp = std::stoul(valueStr, nullptr);
1302 if (tmp > std::numeric_limits<decltype(value16)>::max())
1303 {
1304 throw std::out_of_range("Out of range");
1305 }
1306 value16 = static_cast<decltype(value16)>(tmp);
1307 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001308 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301309 {
1310 log<level::ERR>("Exception for MaxLoginAttemptBeforLockout",
1311 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001312 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301313 }
1314 AccountPolicyIface::maxLoginAttemptBeforeLockout(value16);
1315 }
1316 valueStr.clear();
1317 if (getPamModuleArgValue(pamTally2, unlockTimeout, valueStr) != success)
1318 {
1319 AccountPolicyIface::accountUnlockTimeout(0);
1320 }
1321 else
1322 {
1323 uint32_t value32 = 0;
1324 try
1325 {
1326 tmp = std::stoul(valueStr, nullptr);
1327 if (tmp > std::numeric_limits<decltype(value32)>::max())
1328 {
1329 throw std::out_of_range("Out of range");
1330 }
1331 value32 = static_cast<decltype(value32)>(tmp);
1332 }
Patrick Williams9638afb2021-02-22 17:16:24 -06001333 catch (const std::exception& e)
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301334 {
1335 log<level::ERR>("Exception for AccountUnlockTimeout",
1336 entry("WHAT=%s", e.what()));
Patrick Venture045b1122018-10-16 15:59:29 -07001337 throw;
Richard Marian Thomaiyar9164fd92018-06-13 16:51:00 +05301338 }
1339 AccountPolicyIface::accountUnlockTimeout(value32);
1340 }
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301341 initUserObjects();
Ratan Gupta1af12232018-11-03 00:35:38 +05301342
1343 // emit the signal
1344 this->emit_object_added();
Richard Marian Thomaiyar9f630d92018-05-24 10:49:10 +05301345}
1346
1347} // namespace user
1348} // namespace phosphor